我是ILNumerics的主要开发人员之一。所以我显然是有偏见的;)但我们对内部机制的披露更多,所以我将对我们的速度“秘密”提供一些见解。
这一切都取决于如何利用系统资源!如果您是纯粹的速度并且需要处理大型数组,您将确保(按重要性排序,最重要的优先)
适当地管理你的记忆!“幼稚”的内存管理会导致性能下降,因为它严重地强调了 GC,导致内存碎片并降低内存局部性(因此缓存性能)。在像 .NET 这样的垃圾收集环境中,这归结为防止频繁的内存分配。在 ILNumerics 中,我们实现了一个高性能内存池以实现这一目标(并确定性地处理临时数组以获得良好、舒适的语法,而没有笨拙的函数语义)。
利用并行性!这既针对:线程级并行性,也针对数据级并行性。通过对计算的计算密集部分进行线程化来利用多个内核。在 X86/X64 CPU 上,SSE.XX 和 AVX 等 SIMD/多媒体扩展允许小而有效的矢量化。当前的 .NET 语言无法直接寻址它们。这就是为什么 MKL 可能仍然比“纯”.NET 代码更快的唯一原因。(但解决方案已经在增加。)
要获得高度优化的语言(如 FORTRAN 和 C++)的速度,必须将相同的优化应用到您的代码中。C# 提供了这样做的选项。
请注意,这些预防措施应按此顺序执行!如果瓶颈是内存带宽并且处理器花费大部分时间等待新数据,那么关心 SSE 扩展甚至绑定检查删除是没有意义的。此外,对于许多简单的操作,投入巨大的精力来将最后的微小规模提升到最高性能甚至都不值得!考虑 LAPACK 函数 DAXPY 的常见示例。它将向量 X 的元素添加到另一个向量 Y 的对应元素中。如果这是第一次这样做,X 和 Y 的所有内存都必须从主内存中获取。你几乎无能为力。而且内存是瓶颈!所以不管最后的加法是否在 C# 中以天真的方式完成
for (int i = 0; i < C.Length; i++) {
C[i] = X[i] + Y[i];
}
或通过使用矢量化策略完成 - 它必须等待内存!
我知道,这个答案以某种方式“过度回答”了这个问题,因为大多数这些策略目前还没有从提到的产品中使用(还没有?)。通过遵循这些要点,您最终将获得比“本机”语言中的每个幼稚实现更好的性能。
如果您有兴趣,您可以透露您的 L-BFGS 实施吗?我很乐意将其转换为 ILNumerics 并发布比较结果,我敢肯定,此处列出的其他库也希望跟进。(?)