【问题标题】:F# performance in scientific computing科学计算中的 F# 性能
【发布时间】:2011-02-14 16:26:16
【问题描述】:

我很好奇 F# 性能与 C++ 性能相比如何?我问了一个关于 Java 的类似问题,我得到的印象是 Java 不适合繁重的数字运算。

我读到 F# 应该更具可扩展性和更高的性能,但与 C++ 相比,这种实际性能如何?有关当前实施的具体问题是:

  • 它在浮点方面做得如何?
  • 是否允许向量指令
  • 对优化有多友好 编译器?
  • 它的内存占用有多大?它是否允许对内存位置进行细粒度控制?
  • 是否有分布式的能力 内存处理器,例如 Cray?
  • 它有哪些功能可能对涉及大量数字处理的计算科学感兴趣?
  • 有没有实际的科学计算 使用它的实现?

谢谢

【问题讨论】:

  • 我从标题中删除了 C++ 以使其非对抗性。但是我确实想知道相对于 C++ 的性能(所以我可以联系)

标签: c++ performance f# parallel-processing scientific-computing


【解决方案1】:

与所有语言/性能比较一样,您的里程在很大程度上取决于您的编码能力。

F# 是 OCaml 的派生词。我惊讶地发现 OCaml 在金融世界中被大量使用,其中数字运算性能非常重要。我更惊讶地发现 OCaml 是速度更快的语言之一,其性能与最快的 C 和 C++ 编译器相当。

F# 建立在CLR 之上。在 CLR 中,代码以一种称为通用中间语言的字节码形式表示。因此,它受益于 JIT 的优化功能,如果代码编写得好,它的性能可与 C#(但不一定是 C++)相媲美。

CIL 代码可以在运行前的单独步骤中通过使用本机映像生成器 (NGEN) 编译为本机代码。由于不再需要 CIL 到本机的编译,因此这加快了软件的所有后续运行。

需要考虑的一点是,像 F# 这样的函数式语言受益于更具声明性的编程风格。从某种意义上说,您在 C++ 等命令式语言中过度指定了解决方案,这限制了编译器的优化能力。更具声明性的编程风格理论上可以为编译器提供更多的算法优化机会。

【讨论】:

  • 有趣。我的世界在某种程度上仅限于 fortran 和 C++,但随后试图扩大我的视野。我还没有真正在我的领域看到过 OCaml 应用程序
  • @Robert Harvey——我也听说过 OCaml。超快的性能和小代码。
  • F# 是在 .NET 中实现的,这意味着它继承了它在过度规范方面的一些问题。 F# 函数是幕后的 .NET 方法,它们保证按特定顺序执行,因为它们可能有副作用 - 即使 99% 的时间 F# 不会有这些,或者您不关心它们的顺序(例如调试/记录语句)。所以,我会警告不要期望 F# 有太多的性能——这很好;它可以是合理的快速 - 但它主要是从其功能性质而不是可优化性中获得简洁。
  • 对,所以如果您使用内联函数并且使用无副作用的操作(即没有.NET互操作)然后它可以重新排序。不幸的是,正如可以用反射器验证的那样,普通的 F# 函数编译成 .NET 方法。 MS 本身,在关于内联函数的 MSDN 页面上说:“除非您尝试过所有其他优化技术,否则您应该避免使用内联函数进行优化”。但即使你这样做了,F# 会怎样优化 C++ 中的类似代码(静态内联)?在手动帮助下,我确信 F# 是朝着正确方向迈出的一步 - 但它不是 Haskell。
  • 我想说的并不是 F# 在特定情况下不可能拥有特定的优势,而是不应该让人们相信这些优势是自动的,甚至总是可实现的。从语义上讲,该语言与 C# 并没有什么不同——即使它鼓励您使用在本地范围内无副作用的结构,即使当前编译器比 C# 当前编译器更好地使用该信息。我真的不明白 F# 的语义如何支持更多新的编译器优化,比如 C++。没有灵丹妙药,这...
【解决方案2】:

据我所知,大多数科学计算仍然在 FORTRAN 中完成。对于线性代数问题,它仍然比其他任何东西都快 - 不是 Java,不是 C,不是 C++,不是 C#,不是 F#。 LINPACK 进行了很好的优化。

但是关于“您的里程可能会有所不同”的评论适用于所有基准测试。一揽子陈述(我的除外)很少是真的。

【讨论】:

  • 对不起,我完全看不懂这条评论。
  • 大部分还是fortran,因为惯性(我不认为fortran今天有多大优势)。 linpack 也是如此(已被 lapack 取代)。最近的一些 blas 实现,例如 atlas 和 goto 实际上是 C 和平台内在函数,而不是 fortran。
  • 我承认我的数据已经过时了。但我很想看到今天比较 Fortran 和 C 的线性代数的基准。关键问题:现代商业软件包的供应商使用什么语言?
  • 我不知道。我查看了 mkl 的二进制字符串,这似乎是 C 和 fortran 的混合,更多的是 fortran。但是我会认为会有一些用于内核的大型手动调整程序集。确实会很有趣。
  • 我们用于数值计算的现代商业软件包是用 F# 编写的,它非常高兴地击败了 Fortran。 FFTW 在 MATLAB 中提供了 FFT 例程,并且是用 OCaml 编写的,并且非常愉快地击败了其他一切。
【解决方案3】:

除了别人说的,关于 F# 还有一点很重要,那就是并行性。普通 F# 代码的性能由 CLR 决定,尽管您可以使用 F# 中的 LAPACK,或者您可以使用 C++/CLI 作为项目的一部分进行本机调用。

但是,设计良好的函数式程序往往更容易并行化,这意味着您可以通过使用多核 CPU 轻松获得性能,如果您在进行一些科学计算,那么您绝对可以使用这些 CPU。以下是几个相关链接:

关于分布式计算,您可以使用任何可用于 .NET 平台的分布式计算框架。有一个 MPI.NET 项目,它与 F# 配合得很好,但您也可以使用 DryadLINQ,它是一个 MSR 项目。

【讨论】:

    【解决方案4】:

    不幸的是,我认为您不会找到很多可靠的信息。 F# 仍然是一种非常新的语言,因此即使它非常适合性能繁重的工作负载,也不会有那么多有丰富经验的人可以报告。此外,性能很难准确衡量,微基准也很难泛化。即使在 C++ 中,您也可以看到编译器之间的巨大差异 - 您想知道 F# 是否与 任何 C++ 编译器或假设的“最佳” C++ 可执行文件竞争?

    至于针对 C++ 的特定基准,这里有一些可能相关的链接:O'Caml vs. F#: QR decomposition; F# vs Unmanaged C++ for parallel numerics。请注意,作为 F# 相关材料的作者和 F# 工具的供应商,作者对 F# 的成功有着既得利益,因此请对这些声明持保留态度。

    我认为可以肯定地说,在某些应用程序中,F# 在执行时间上具有竞争力,而在其他一些应用程序中则不然。在大多数情况下,F# 可能需要更多内存。当然,最终的性能也将高度依赖于程序员的技能——我认为 F# 几乎肯定会成为一种更有生产力的语言,可以让中等能力的程序员使用它。此外,我认为目前,Windows 上的 CLR 在大多数操作系统上的大多数任务都比 Mono 执行得更好,这也可能会影响您的决定。当然,由于 F# 可能比 C++ 更容易并行化,它还取决于您计划运行的硬件类型。

    最终,我认为真正回答这个问题的唯一方法是编写代表您要执行的计算类型的 F# 和 C++ 代码并比较它们。

    【讨论】:

    • F# compiler 可能是新的(F# 编译器为此生成的代码的性能未知),但 F# 的面向函数的部分远非新的。它可以不加任何更改(这仅适用于以特定方式编写的 F#)被编译为已经存在了几个世纪的 OCaml。如果 F# 中的优化器与 OCaml 优化器相当,那么 OCaml 被证明是一种对优化器非常友好的语言(由于其不变性),那么繁重的数字运算非常适合 F#
    • @RuneFS - 在 O'Caml 中获得良好的性能通常是以不使用其更高级别的构造为代价的(例如,参见 janestreetcapital.com/minsky_weeks-jfp_18.pdf 的第 3.3 节)。当谈到现实世界中的 F# 性能时,当前唯一的 F# 实现在 .NET(CLR 或 Mono)上运行这一事实也意味着某些优化可能不可用。我是 F# 的忠实拥护者,未来进一步的优化可能会提供更快的速度,但目前我怀疑有许多应用程序的“最佳”C++ 代码会胜过“最佳”F# 代码。
    • F# 运行得足够快。我不希望它的编译器能够大幅改进。该语言的核心仍然是一种允许副作用的语言,它保证了特定的执行顺序;极大地限制了优化。例如let f x y = (expensive x |> g) y 与 F# 中的 let f x = expensive x |> g 根本不同,尽管它们在函数世界中的语义是等价的。
    • @Eamon - 肯定有挑战。但是,我认为你的立场过于黯淡。因为 F# 在 CLR 上运行,所以对 F# 编译器本身或 CLR JIT 的改进都会影响性能。可能有很多 .NET JIT 编译器可以得到显着改进的地方(例如,跳过更广泛的可证明不必要的数组边界检查、内联启发式改进等)。鉴于这是由一个小团队创建的语言的第一个生产版本,如果进一步努力可以改进 F# 编译器的输出,我也不会感到惊讶。
    • 纯度注释可能是性能的一大胜利。我并不是要贬低 F#——只是我在代码简洁性和可读性方面看到了它的好处,而不是期待许多性能好处。我宁愿人们出于这些原因选择 F#,因为他们认为 perf 更好——然后在他们发现它很少有的时候丢弃它。至于新的和改进的 CLR 优化:CLR 已有 10 年历史。虽然它肯定不是完美的,但我不会再指望彻底的性能增强了。明显的改进已经完成了。
    【解决方案5】:

    这取决于你在做什么样的科学计算。

    如果你正在做traditional heavy computing,例如线性代数,各种优化,那么你不应该把你的代码放在.Net框架中,至少不适合F#。因为这是在算法级别,所以大多数算法必须用命令式语言编码,才能在运行时间和内存使用方面具有良好的性能。其他人提到并行,我必须说,当您执行并行 SVD 实现之类的低级工作时,它可能毫无用处。因为当您知道如何并行 SVD 时,您根本不会使用高级语言,Fortran、C 或修改后的 C(例如 cilk)是您的朋友。

    但是,今天的许多科学计算都不是这种类型,而是某种高级应用程序,例如。统计计算和数据挖掘。在这些任务中,除了一些线性代数,或者优化之外,还有大量的数据流、IO、预处理、做图形等。对于这些任务,F#真的很强大,因为它简洁、实用、安全、易于并行等。

    正如其他人所说,.Net很好地支持Platform Invoke,实际上MS内部有不少项目是同时使用.Net和P/Invoke来提高瓶颈处的性能。

    【讨论】:

    • "在算法层面,大多数算法必须用命令式语言编码,才能在运行时间和内存使用方面具有良好的性能" [需要引用]
    • 这些算法的运行时间是以失败来衡量的,高级语言很难衡量这一点。内存使用量也很难预测,在 C 和 Fortran 中,您可以准确计算您将使用多少字节。
    • “通过检查命令式语言更容易找出性能”与“只有命令式语言才能提供​​良好的性能”非常不同。而且也错了。缓存一致性等二阶效应对现代处理器非常重要,以至于在 FLOP 中测量算法毫无价值。在 FLOP 优化算法和需要 10 倍 FLOP 的局部优化算法之间,局部优化算法将获胜。跟我重复一遍:FPU 不再是瓶颈。
    【解决方案6】:

    这里有两个我可以分享的例子:

    1. 矩阵乘法: 我有一篇博文comparing different matrix multiplication implementations

    2. LBFGS

    我有一个使用 LBFGS 优化的大规模逻辑回归求解器,它是用 C++ 编码的。实现是很好的调整。我将一些代码修改为 C++/CLI 中的代码,即将代码编译成 .Net。 .Net 版本比在不同数据集上简单编译的版本慢 3 到 5 倍。如果您在 F# 中编写 LBFGS,则性能不会比 C++/CLI 或 C# 好,(但会非常接近)。

    我在Why F# is the language for data mining 上有另一个帖子,虽然与您在这里关注的性能问题不太相关,但它与 F# 中的科学计算有很大关系。

    【讨论】:

    • -1:这不是真的:“如果您在 F# 中编写 LBFGS,性能不会比 C++/CLI 或 C# 好,(但会非常接近)。”。这正是 F# 比 C# 快 很多 的应用程序。
    • @Jon 为什么?你的意思是“平行”吗?
    • @Jon。我已经编写了 LBFGS 代码,我知道提高性能和内存使用率的技巧,这些技巧必须以命令式风格进行编码。 FP 在这里似乎有很好的设计模式,但性能与样式关系不大,尤其是对于高度优化的数字代码。在使用 LBFGS 的大多数问题中,时间成本主要是在函数值和梯度计算上,少数都是在 LBFGS 本身中使用的。如果 LBFGS 或线搜索迭代比函数值和梯度中的计算多得多,则使其​​内联确实会提高性能。然而,这通常不是真的。
    • 第二,我没有看到直接将向量(数组指针)传递给函数的性能问题,运行它并返回另一个指向梯度数组的指针。如果这个函数只花费一点时间,当交互中有一些开销时,内联会有所帮助。因为梯度数组通常很大,(这就是我们需要 Limitedmemory-BFGS 的原因),我们必须确保梯度数组被预先分配并在未来的迭代中重用。在这种东西的实现中只是有很多必要的思考。
    • 不,inline 在 F# 中的主要好处不是它消除了函数调用的开销,而是它使 CLR 对您的代码进行类型专门化。如果您的 LBFGS 仅处理 float arrayvector 输入和输出,那么您已经针对一种特定情况手动对其进行了专门的类型化,这使得它的用处大大降低。通用 BFGS 实现应该使用用户提供的函数直接在用户的数据结构中读取其输入并将其输出写入。 F# 在这里比 C# 有巨大的性能优势。
    【解决方案7】:
    • F# 以 .NET CLR 允许的速度执行浮点计算。与 C# 或其他 .NET 语言没有太大区别。
    • F# 本身不允许向量指令,但如果您的 CLR 有用于这些的 API,F# 使用它应该不会有问题。参见例如Mono
    • 据我所知,目前只有一个 F# 编译器,所以问题应该是“F# 编译器在优化方面有多好?”。无论如何,答案是“可能与 C# 编译器一样好,但目前可能更差一些”。请注意,F# 不同于例如C# 支持在编译时内联,这可能允许更高效的代码依赖泛型。
    • F# 程序的内存占用与其他 .NET 语言的类似。您对分配和垃圾回收的控制量与其他 .NET 语言相同。
    • 不知道是否支持分布式内存。
    • F# 具有非常好的处理平面数据结构的原语,例如数组和列表。例如看看 Array 模块的内容:map、map2、mapi、iter、fold、zip... 数组在科学计算中很流行,我猜是因为它们固有的良好的内存局部性属性。
    • 对于使用 F# 的科学计算包,您可能想看看 Jon Harrop 正在做什么。

    【讨论】:

    • 我只想指出问题是 F# vs C++ 而这个答案是 F# vs C# 并且 C++ 和 C# 是不同的语言。
    【解决方案8】:

    我很好奇 F# 性能与 C++ 性能相比如何?

    因应用程序而异。如果您在多线程程序中广泛使用复杂的数据结构,那么 F# 可能会是一个巨大的胜利。如果你的大部分时间都花在紧密的数值循环上,那么 C++ 可能会快 2-3 倍。

    案例研究:光线追踪器 我的基准测试here 使用树进行分层剔除和数值光线球相交代码来生成输出图像。这个基准已经有几年的历史了,C++ 代码多年来已经改进了数十次,并被数十万人阅读。 Microsoft 的 Don Syme 设法编写了一个 F# 实现,当使用 MSVC 编译并使用 OpenMP 并行化时,它比最快的 C++ 代码略

    我了解到 F# 应该更具可扩展性和性能,但与 C++ 相比,这种实际性能如何?

    使用 F# 开发代码比使用 C++ 更容易和更快,这适用于优化和维护。因此,当您开始优化程序时,如果您使用 F# 而不是 C++,同样的工作量将产生更大的性能提升。但是,F# 是一种高级语言,因此性能上限较低。因此,如果您有无限时间进行优化,理论上您应该始终能够在 C++ 中生成更快的代码。

    当然,这与 C++ 相对于 Fortran 和 Fortran 相对于手写汇编程序的优势完全相同。

    案例研究:QR 分解 这是 LAPACK 等库提供的线性代数的基本数值方法。参考 LAPACK 实现是 2,077 行 Fortran。我用不到 80 行代码编写了an F# implementation,达到了相同的性能水平。但参考实现并不快:英特尔的数学内核库 (MKL) 等供应商调整的实现通常快 10 倍。值得注意的是,我设法优化了我的 F# 代码, 超越了在英特尔硬件上运行的英特尔实现的性能,同时将我的代码保持在 150 行代码以下并且完全通用(它可以处理单精度和双精度,以及复杂的甚至符号矩阵!):对于高瘦矩阵,我的 F# 代码比英特尔 MKL 快 3 倍。

    请注意,本案例研究的寓意并不是您应该期望您的 F# 比供应商调整的库更快,而是即使像英特尔这样的专家如果只使用较低级别的优化,他们也会错过高效的高级优化语言。我怀疑英特尔的数值优化专家未能充分利用并行性,因为他们的工具使其极其繁琐,而 F# 使其毫不费力。

    它在浮点方面做得如何?

    性能类似于 ANSI C,但某些功能(例如舍入模式)在 .NET 中不可用。

    是否允许向量指令

    没有。

    它对优化编译器有多友好?

    这个问题没有意义:F# 是 Microsoft 的专有 .NET 语言,只有一个编译器。

    它的内存占用有多大?

    一个空的应用程序在这里使用 1.3Mb。

    它是否允许对内存位置进行细粒度控制?

    优于大多数内存安全语言,但不如 C。例如,您可以通过将 F# 中的任意数据结构表示为“结构”来拆箱。

    它是否具有分布式内存处理器的容量,例如 Cray?

    取决于您所说的“容量”。如果您可以在该 Cray 上运行 .NET,那么您可以使用 F# 中的消息传递(就像下一种语言一样),但 F# 主要用于桌面多核 x86 机器。

    它有哪些特征可能对涉及大量数字处理的计算科学感兴趣?

    内存安全意味着您不会遇到分段错误和访问冲突。 .NET 4 对并行性的支持很好。在 Visual Studio 2010 中通过 F# 交互式会话即时执行代码的能力对于交互式技术计算非常有用。

    是否有使用它的实际科学计算实现?

    我们在 F# 中用于科学计算的 commercial products 已经拥有数百名用户。

    但是,您的提问表明您认为科学计算是高性能计算(例如 Cray)而不是交互式技术计算(例如 MATLAB、Mathematica)。 F# 适用于后者。

    【讨论】:

    • 在我早期的 cmets 中,我正在考虑您所说的高性能计算,而不是交互式。
    • 您还没有完全发布据称优于 MATLAB 的 F# 实现 :-)
    • @Jon Harrop '内存位置?比大多数内存安全语言好,但不如 C' C 存在哪些用于这种局部性控制的选项,而在 F# 中没有这些选项?这是语言还是平台限制?谢谢
    • @user492238:在 C 中,您可以做一些事情,例如在指针中走私位并获取指向堆分配内存块中间的内部指针。垃圾收集语言几乎总是禁止这样做。所以有一些牺牲,但相对较小。
    • 这篇文章充满了未经证实的断言。 F# 让您轻松创建比 C++ 更高性能的代码的想法尤其值得怀疑。我已经非常深入地参与了 F#,包括许多用于加速高阶 Array 函数的 PR,我可以向您保证,通常情况并非如此。 F# 的创建者可以在 F# 中创建比在 C++ 中更快的东西,这可能更能说明您在每种语言中的相关才能,而不是它们的任何与生俱来的属性。
    【解决方案9】:

    首先,C 比 C++ 快得多。所以如果你需要这么快的速度,你应该在 c 中制作 lib 等。

    关于 F#,大多数基准测试使用 Mono,它比 MS CLR 慢 2 * 部分原因是它使用了 boehm GC(他们有一个新的 GC 和 LVVM,但这些仍然不成熟,不支持泛型等) .

    .NET 语言本身被编译为 IR(CIL),它可以像 C++ 一样高效地编译为本机代码。大多数 GC 语言都存在一个问题,那就是大量的可变写入(这包括上面提到的 C++ .NET)。并且有一个特定的科学问题集需要这样做,这些在需要时可能应该使用本机库或使用享元模式来重用池中的对象(这减少了写入)。原因是 .NET CLR 中存在写入障碍,当更新引用字段(包括框)时,它将在表中设置一个位,说明该表已修改。如果您的代码包含大量此类写入,它将受到影响。

    也就是说,像 C# 这样的 .NET 应用程序使用大量静态代码、结构和结构上的 ref/out 可以产生类似 C 的性能,但很难像这样编写代码或维护代码(如 C)。

    然而,F# 的亮点在于不可变数据的并行性,这与更多基于读取的问题密切相关。值得注意的是,可变写入中的大多数基准比现实生活中的应用程序要高得多。

    关于浮点,您应该使用 oCaml 的替代库(即 .Net 库),因为它很慢。 C/C++ 允许更快的低精度,而 oCaml 默认情况下不允许。

    最后,我认为像 C#、F# 这样的高级语言和适当的分析将在相同的开发时间为您提供比 c 和 C++ 更好的性能。如果您将瓶颈更改为 c lib pinvoke 调用,您最终也会在关键区域获得类似 C 的性能。也就是说,如果您有无限的预算并且更关心速度,那么维护比 C 是要走的路(不是 C++)。

    【讨论】:

      【解决方案10】:

      如果我说“2-3 年后再问”,我认为这将完全回答您的问题 :-)

      首先,不要期望 F# 在性能方面与 C# 有任何不同,除非您故意进行一些复杂的递归,而且我猜您不会因为您询问数字而有所不同。

      在浮点方面它肯定会比 Java 更好,因为 CLR 不针对跨平台统一性,这意味着 JIT 将尽可能地使用 80 位。另一方面,除了观察变量的数量以确保有足够的 FP 寄存器之外,您无法控制它。

      在矢量方面,如果你尖叫得足够大声,可能会在 2-3 年内发生一些事情,因为 Direct3D 无论如何都作为通用 API 进入 .NET 并且在 XNA 中完成的 C# 代码在 Xbox 上运行,而你可以尽可能接近裸机使用 CLR。这仍然意味着您需要自己执行一些中间代码。

      因此,不要指望 CUDA 甚至能够链接 NVIDIA 库并开始工作。如果出于某种原因你真的非常需要一种“函数式”语言,那么你会更幸运地尝试使用 Haskell 的这种方法,因为 Haskell 被设计为出于纯粹的需要而对链接友好。

      Mono.Simd 已经被提及,虽然它应该可以向后移植到 CLR,但实际执行它可能需要相当多的工作。

      social.msdn posting 中有很多关于在 .NET、vith C++/CLI 和 C# 中使用 SSE3、数组 blitting、为性能注入 SSE3 代码等的代码。

      有人谈论在编译后的 C# 上运行 CECIL 以将部分提取到 HLSL、编译到着色器并链接胶水代码以安排它(CUDA 无论如何都在做等价),但我认为没有任何可运行的东西从中走出来。

      如果您想尽快尝试,可能对您更有价值的是PhysX.Net on codeplex。不要指望它只是打开包装并施展魔法。但是,ih 目前有活跃的作者,代码都是普通的 C++ 和 C++/CLI,如果你想深入了解细节并可能对 CUDA 使用类似的方法,yopu 可能会从作者那里得到一些帮助。对于全速 CUDA,您仍然需要编译自己的内核,然后只需与 .NET 接口,这样部分越容易,您就会越快乐。

      有一个CUDA.NET 库,它应该是免费的,但该页面只提供了电子邮件地址,所以期待附加一些字符串,虽然作者写了一个blog,但他并没有特别谈论库中的内容。

      哦,如果你有预算,你可以看看Psi Lambda(KappaCUDAnet 是 .NET 的一部分)。显然他们将在 11 月抬高价格(如果这不是销售技巧的话 :-)

      【讨论】:

      • 模式匹配的优化是 F# 有潜力做很多事情但 C# 什么都不做的一个领域。这与科学计算中的符号计算有关。并非巧合的是,世界上一些最大的符号计算是用 F# 的前身 OCaml 编写的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-07-07
      • 1970-01-01
      • 2013-01-29
      • 1970-01-01
      • 1970-01-01
      • 2010-10-20
      相关资源
      最近更新 更多