c# - 为什么这个简单的 F# 代码比 C#/C++ 版本慢 36 倍?

标签 c# c++ performance f#

我编写了一个简单的测试,它创建了一个变量,将其初始化为零并递增 100000000 次。

C++ 在 0.36 秒内完成。原始 C# 版本 0.33s 新版本 0.8s F# 12 秒。

我没有使用任何函数,所以默认情况下问题不在于泛型

F# 代码

open System
open System.Diagnostics
// Learn more about F# at http://fsharp.org
// See the 'F# Tutorial' project for more help.
[<EntryPoint>]
let main argv = 
    let N = 100000000
    let mutable x = 0
    let watch = new Stopwatch();
    watch.Start();
    for i in seq{1..N} do
        x <- (x+1)
    printfn "%A" x
    printfn "%A" watch.Elapsed
    Console.ReadLine()
        |> ignore
    0 // return an integer exit code

C++ 代码

#include<stdio.h>
#include<string.h>
#include<vector>
#include<iostream>
#include<time.h>
using namespace std;
int main()
{
    const int N = 100000000;
    int x = 0;
    double start = clock();
    for(int i=0;i<N;++i)
    {
        x = x + 1;
    }
    printf("%d\n",x);
    printf("%.4lf\n",(clock() - start)/CLOCKS_PER_SEC);
    return 0;
}

C#代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace SpeedTestCSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            const int N = 100000000;
            int x = 0;
            Stopwatch watch = new Stopwatch();
            watch.Start();

            foreach(int i in Enumerable.Range(0,N))
            //Originally it was for(int i=0;i<N;++i)
            {
                x = x + 1;
            }
            Console.WriteLine(x);
            Console.WriteLine(watch.Elapsed);
            Console.ReadLine();
        }
    }
}

编辑

替换 for (int i = 0; i < N; ++i)foreach(int i in Enumerable.Range(0,N))让 C# 程序运行在 0.8s 左右,但仍然比 f# 快很多

编辑

已替换 DateTimeStopWatch对于 F#/C#。结果是一样的

最佳答案

这肯定是由于使用表达式而直接发生的:

for i in seq{1..N} do

在我的机器上,结果如下:

100000000

00:00:09.1500924

如果我将循环更改为:

for i in 1..N do

结果发生巨大变化:

100000000

00:00:00.1001864

为什么?

这两种方法生成的 IL 是完全不同的。第二种情况,使用 1..N语法的编译方式与 C# for(int i=1; i<N+1; ++i) 相同。循环。

第一种情况完全不同,这个版本产生一个完整的序列,然后由 foreach 循环枚举。

使用 IEnumerables 的 C# 和 F# 版本不同之处在于它们使用不同的范围函数来生成它们。

C#版本使用System.Linq.Enumerable.RangeIterator生成值范围,而 F# 版本使用 Microsoft.FSharp.Core.Operators.OperatorIntrinsics.RangeInt32 .我认为可以肯定地假设我们在这种特殊情况下看到的 C# 和 F# 版本之间的性能差异是这两个函数的性能特征的结果。

svick 在他的评论中指出 + 是正确的。运算符实际上是作为参数传递给 integralRangeStep功能。

对于不平凡的情况,n <> m这导致 F# 编译器使用 ProperIntegralRangeEnumerator在这里找到实现:https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs#L6463

let inline integralRangeStepEnumerator (zero,add,n,step,m,f) : IEnumerator<_> =
    // Generates sequence z_i where z_i = f (n + i.step) while n + i.step is in region (n,m)
    if n = m then
        new SingletonEnumerator<_> (f n) |> enumerator 
    else
        let up = (n < m)
        let canStart = not (if up then step < zero else step > zero) // check for interval increasing, step decreasing 
        // generate proper increasing sequence
        { new ProperIntegralRangeEnumerator<_,_>(n,m) with 
                member x.CanStart = canStart
                member x.Before a b = if up then (a < b) else (a > b)
                member x.Equal a b = (a = b)
                member x.Step a = add a step
                member x.Result a = f a } |> enumerator

我们可以看到单步执行枚举器会导致调用提供的 add函数而不是更直接、更直接的添加。

注意:所有计时都在 Release模式下运行(尾调用:开启,优化:开启)。

关于c# - 为什么这个简单的 F# 代码比 C#/C++ 版本慢 36 倍?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35378119/

相关文章:

c# - 如何在 C# 中使用 SSH.NET 库连接 IPv6 地址

c++ - 为什么 vector::erase() 不会使对已删除元素的引用无效?

Mysql - 有没有更好的方法来运行大量数据的子查询?

c++ - C++ 中序列化对象的性能

c# - 如何在不使用 ASP.NET 服务器端控件的情况下从 C# ASP.NET 中的 HTML 文件类型读取输入流

c# - Linux 系统在 .NET Core 应用程序上引发错误

c# - ASP.NET如何从SQL数据库获取当前登录的用户名和ID

c++ - 非类型模板——哪些类型是允许的?

c++ - 未插入 Windows API 组合框数据

algorithm - 计算时间复杂度的最简单方法?