f#似乎比其他语言慢…我能做些什么来加速它?

时间:2021-04-01 03:53:56

I like F# ; I really, really do. Having been bitten by the "functional programming"-bug, I force myself to use it when I have the opportunity to. In fact, I recently used it (during a one week vacation) to code a nice AI algorithm.

我喜欢f#;我真的,真的。由于被“函数式编程”bug所困扰,我强迫自己在有机会的时候使用它。事实上,我最近用它(在一周的假期中)编写了一个很好的人工智能算法。

However, my attempts so far (see a SO question related to my first attempt here) seem to indicate that, though undoubtedly beautiful... F# has the slowest execution speed of all the languages I've used.

然而,到目前为止,我的尝试似乎表明了这一点,尽管这无疑是美丽的……f#的执行速度是我所使用的所有语言中最慢的。

Am I doing something wrong in my code?

我的代码做错了吗?

I verbosely explain what I did in my blog post, and in my experiments, I see OCaml and the rest of the group running anywhere from 5x to 35x faster than F#.

在我的博客文章中,我详细地解释了我所做的事情,在我的实验中,我看到了OCaml和其他团队的运行速度,从5x到35x,比f#要快。

Am I the only one with such experiences? I find it disheartening that the language I like the most, is also the slowest one - sometimes by far...

我是唯一有这种经历的人吗?我发现我最喜欢的语言也是最慢的,有时甚至是最慢的。

EDIT: Direct GitHub link, where the code lives in various language forms...

编辑:直接GitHub链接,代码以各种语言形式存在……

EDIT2: Thanks to Thomas and Daniel, speed improved considerably:

EDIT2:感谢Thomas和Daniel,速度大大提高了:

  • Greatest speed boost: moving from "ref" to "mutable" gave a whopping 30%.
  • 最大速度提升:从“ref”移动到“可变”的速度高达30%。
  • Removing exceptions and using while/flagChecks gave another 16%.
  • 删除异常和使用while/ flagcheck则提供了另外的16%。
  • Switching from discriminated unions to enums gave another 5%.
  • 从受歧视的工会转到enums,再给了5%。
  • "inline" gave 0.5-1%
  • “内联”给了0.5 - -1%

EDIT3: Dr Jon Harrop joined the fight: 60% speedup, by making ScoreBoard operate directly on the "enumerated" version of the data. The imperative version of F# now runs 3-4 times slower than C++, which is a good result for a VM-based runtime. I consider the problem solved - thanks guys!

EDIT3: Jon Harrop博士加入了这场战斗:60%的加速,通过让记分牌直接运行在“点算”的数据版本上。f#的命令版本现在运行速度比c++慢3-4倍,这对于基于vm的运行时来说是一个很好的结果。我认为问题解决了-谢谢大家!

EDIT4: After merging all optimizations, these are the results (F# reached C# in imperative style - now if only I could do something about functional style, too!)

EDIT4:在合并所有优化之后,这些是结果(f#在命定风格中达到c# -现在如果我也能做一些功能风格的事情!)

  • real 0m0.221s: That was C++
  • 真正的0m0.221s:那是c++。
  • real 0m0.676s: That was C# (imperative, C++ mirror)
  • 真正的0m0.676s:那是c#(命令,c++镜像)
  • real 0m0.704s: That was F# (imperative, C++ mirror)
  • 真正的0m0.704s:那是f#(命令,c++镜像)
  • real 0m0.753s: That was OCaml (imperative, C++ mirror)
  • 真正的0m0.753s:那是OCaml(命令,c++镜像)
  • real 0m0.989s: That was OCaml (functional)
  • real 0m0.989s: OCaml(功能)
  • real 0m1.064s: That was Java (imperative)
  • real 0m1.064s:这是Java(命令式)
  • real 0m1.955s: That was F# (functional)
  • real 0m1.955s:那是f#(功能)

2 个解决方案

#1


15  

Unless you can give a reasonably sized code sample, it's difficult to tell. Anyway, the imperative F# version should be as efficient as the imperative C# version. I think one approach is to benchmark the two to see what is causing the difference (then someone can help with making that bit faster).

除非你能给出一个相当大的代码样本,否则很难判断。无论如何,必要的f#版本应该和命令式的c#版本一样有效。我认为一种方法是将两者作为基准,看看是什么导致了差异(然后有人可以帮助更快地实现这一点)。

I briefly looked at your code and here are some assorted (untested) suggestions.

我简要地看了一下您的代码,这里有一些(未经测试的)建议。

  • You can replace discriminated union Cell with an enum (this means you'll use value types and integer comparison instead of reference types and runtime type tests):

    您可以使用enum替换有区别的union单元(这意味着您将使用值类型和整数比较而不是引用类型和运行时类型测试):

    type Cell =    
      | Orange = 1
      | Yellow = 2
      | Barren = 3
    
  • You can mark some trivial functions as inline. For example:

    可以将一些简单的函数标记为内联。例如:

    let inline myincr (arr:int array) idx =
      arr.[idx] <- arr.[idx] + 1
    
  • Don't use exceptions for control-flow. This is often done in OCaml, but .NET exceptions are slow and should be only used for exceptions. You can replace the for loop in your sample with a while loop and a mutable flag or with a tail-recursive function (a tail-recursive function is compiled into a loop, so it will be efficient, even in imperative solution).

    不要使用异常来控制流。这通常在OCaml中完成,但是。net异常是缓慢的,应该只用于异常。您可以使用while循环和可变标志或尾部递归函数来替换示例中的for循环(尾部递归函数被编译成循环,因此它将是高效的,甚至在必要的解决方案中也是如此)。

#2


12  

This isn't an answer, per se, but have you tried writing the exact same code in F# and C#, i.e., imperative F# code? The speed should be similar. If you're comparing terse functional code with heavy use of higher-order functions, sequence expressions, lazy values, complex pattern matching, etc.--all things that allow for shorter, clearer (read, more maintainable) code--well, there is frequently a trade-off. Generally, development/maintenance time is much greater than execution time, so it's usually considered a desirable trade-off.

这并不是一个答案,但您尝试过在f#和c#中编写完全相同的代码,即。,必要的f#代码?速度应该是相似的。如果您正在比较terse函数代码和大量使用高阶函数、序列表达式、延迟值、复杂模式匹配等等——所有这些都允许更短、更清晰(读、更可维护)的代码——那么,常常需要权衡。一般来说,开发/维护时间比执行时间要大得多,因此通常认为这是一种可取的折衷。

Some references:
F# and C# 's CLR is same then why is F# faster than C#
C# / F# Performance comparison
https://*.com/questions/142985/is-a-program-f-any-more-efficient-execution-wise-than-c

一些参考资料:f#和c# 's CLR是相同的,那么为什么f#要比c# c# / f#的性能比较快,而https://*.com/questions/142985/is-a-program- F -any-more-效率-执行-比C。

Another point to consider: in a functional language you're working at a higher level and it becomes very easy to overlook the costs of operations. For example, Seq.sort seems innocent enough, but naive use of it can doom performance. I'd recommend poring over your code, asking yourself along the way if you understand the cost of each operation. If you're not feeling reflective, a faster way to do this is, of course, with a profiler.

另一个需要考虑的问题是:在函数式语言中,您在更高的层次上工作,并且很容易忽略操作的成本。例如,Seq。sort似乎是很天真的,但是天真地使用它会使性能降低。我建议你仔细阅读你的代码,如果你了解每一个操作的成本,就问问自己。如果你不喜欢思考,那么一个更快的方法就是用一个分析器。

#1


15  

Unless you can give a reasonably sized code sample, it's difficult to tell. Anyway, the imperative F# version should be as efficient as the imperative C# version. I think one approach is to benchmark the two to see what is causing the difference (then someone can help with making that bit faster).

除非你能给出一个相当大的代码样本,否则很难判断。无论如何,必要的f#版本应该和命令式的c#版本一样有效。我认为一种方法是将两者作为基准,看看是什么导致了差异(然后有人可以帮助更快地实现这一点)。

I briefly looked at your code and here are some assorted (untested) suggestions.

我简要地看了一下您的代码,这里有一些(未经测试的)建议。

  • You can replace discriminated union Cell with an enum (this means you'll use value types and integer comparison instead of reference types and runtime type tests):

    您可以使用enum替换有区别的union单元(这意味着您将使用值类型和整数比较而不是引用类型和运行时类型测试):

    type Cell =    
      | Orange = 1
      | Yellow = 2
      | Barren = 3
    
  • You can mark some trivial functions as inline. For example:

    可以将一些简单的函数标记为内联。例如:

    let inline myincr (arr:int array) idx =
      arr.[idx] <- arr.[idx] + 1
    
  • Don't use exceptions for control-flow. This is often done in OCaml, but .NET exceptions are slow and should be only used for exceptions. You can replace the for loop in your sample with a while loop and a mutable flag or with a tail-recursive function (a tail-recursive function is compiled into a loop, so it will be efficient, even in imperative solution).

    不要使用异常来控制流。这通常在OCaml中完成,但是。net异常是缓慢的,应该只用于异常。您可以使用while循环和可变标志或尾部递归函数来替换示例中的for循环(尾部递归函数被编译成循环,因此它将是高效的,甚至在必要的解决方案中也是如此)。

#2


12  

This isn't an answer, per se, but have you tried writing the exact same code in F# and C#, i.e., imperative F# code? The speed should be similar. If you're comparing terse functional code with heavy use of higher-order functions, sequence expressions, lazy values, complex pattern matching, etc.--all things that allow for shorter, clearer (read, more maintainable) code--well, there is frequently a trade-off. Generally, development/maintenance time is much greater than execution time, so it's usually considered a desirable trade-off.

这并不是一个答案,但您尝试过在f#和c#中编写完全相同的代码,即。,必要的f#代码?速度应该是相似的。如果您正在比较terse函数代码和大量使用高阶函数、序列表达式、延迟值、复杂模式匹配等等——所有这些都允许更短、更清晰(读、更可维护)的代码——那么,常常需要权衡。一般来说,开发/维护时间比执行时间要大得多,因此通常认为这是一种可取的折衷。

Some references:
F# and C# 's CLR is same then why is F# faster than C#
C# / F# Performance comparison
https://*.com/questions/142985/is-a-program-f-any-more-efficient-execution-wise-than-c

一些参考资料:f#和c# 's CLR是相同的,那么为什么f#要比c# c# / f#的性能比较快,而https://*.com/questions/142985/is-a-program- F -any-more-效率-执行-比C。

Another point to consider: in a functional language you're working at a higher level and it becomes very easy to overlook the costs of operations. For example, Seq.sort seems innocent enough, but naive use of it can doom performance. I'd recommend poring over your code, asking yourself along the way if you understand the cost of each operation. If you're not feeling reflective, a faster way to do this is, of course, with a profiler.

另一个需要考虑的问题是:在函数式语言中,您在更高的层次上工作,并且很容易忽略操作的成本。例如,Seq。sort似乎是很天真的,但是天真地使用它会使性能降低。我建议你仔细阅读你的代码,如果你了解每一个操作的成本,就问问自己。如果你不喜欢思考,那么一个更快的方法就是用一个分析器。