18

根据我的经验,.NET 比本机代码慢 2 到 3 倍。(我为多元优化实施了 L-BFGS)。

我已将 stackoverflow 上的广告追踪到 http://www.centerspace.net/products/

速度真的很惊人,速度接近原生代码。他们怎么能这样做?他们说:

问:NMath 是“纯”.NET 吗?

A. 答案在某种程度上取决于您对“纯 .NET”的定义。NMath 是用 C# 编写的,外加一个小的托管 C++ 层。然而,为了获得更好的基本线性代数运算性能,NMath 确实依赖于本机英特尔数学内核库(包含在 NMath 中)。但是没有 COM 组件,没有 DLL——只有 .NET 程序集。此外,在托管 C++ 层中分配并由本机代码使用的所有内存都是从托管堆中分配的。

有人可以向我解释更多吗?

4

8 回答 8

11

他们怎么能这样做?

与 .NET 的大多数数值库一样,NMath 只不过是嵌入在 .NET 程序集中的英特尔 MKL 的包装器,可能通过与 C++/CLI 链接来创建混合程序集。您可能刚刚对那些实际上不是用 .NET 编写的位进行了基准测试。

F#.NET 期刊文章数值库:特殊函数、插值和随机数(2008 年 3 月 16 日)和数值库:线性代数和谱方法(2008 年 4 月 16 日)测试了相当多的功能,而 NMath 实际上是所有功能中最慢的商业图书馆。他们的 PRNG 比所有其他人慢,比免费的 Math.NET 库慢 50%,缺少一些基本功能(例如计算能力Gamma(-0.5))和其他基本功能(他们确实提供的与 Gamma 相关的功能)被破坏。Extreme Optimization 和 Bluebit 在 eigensolver 基准测试中都击败了 NMath。NMath 当时甚至没有提供傅立叶变换。

更令人惊讶的是,有时性能差异很大。在 FFT 基准测试中,我们测试的最昂贵的商业数值库 (IMSL) 比免费的 FFTW 库慢 500 倍以上,而且当时没有任何库使用多核。

事实上,正是这些库的低质量促使我们将自己的F# for Numerics库(这是 100% 纯 F# 代码)商业化。

于 2010-07-02T18:40:11.680 回答
10

我是ILNumerics的主要开发人员之一。所以我显然是有偏见的;)但我们对内部机制的披露更多,所以我将对我们的速度“秘密”提供一些见解。

这一切都取决于如何利用系统资源!如果您是纯粹的速度并且需要处理大型数组,您将确保(按重要性排序,最重要的优先)

  1. 适当地管理你的记忆!“幼稚”的内存管理会导致性能下降,因为它严重地强调了 GC,导致内存碎片并降低内存局部性(因此缓存性能)。在像 .NET 这样的垃圾收集环境中,这归结为防止频繁的内存分配。在 ILNumerics 中,我们实现了一个高性能内存池以实现这一目标(并确定性地处理临时数组以获得良好、舒适的语法,而没有笨拙的函数语义)。

  2. 利用并行性!这既针对:线程级并行性,也针对数据级并行性。通过对计算的计算密集部分进行线程化来利用多个内核。在 X86/X64 CPU 上,SSE.XX 和 AVX 等 SIMD/多媒体扩展允许小而有效的矢量化。当前的 .NET 语言无法直接寻址它们。这就是为什么 MKL 可能仍然比“纯”.NET 代码更快的唯一原因。(但解决方案已经在增加。)

  3. 获得高度优化的语言(如 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 并发布比较结果,我敢肯定,此处列出的其他库也希望跟进。(?)

于 2012-02-17T12:12:13.160 回答
8

关于 C++/CLI 的观点是正确的。为了完成这幅图,还有两个有趣的点:

  • .NET 内存管理(垃圾收集器)显然不是这里的问题,因为 NMath 仍然依赖它

  • 性能优势实际上是由英特尔 MKL 提供的,它为许多 CPU 提供了极其优化的实现。在我看来,这是关键点。使用直截了当的 C/C++ 代码不一定会给您带来优于 C#/.NET 的性能,有时甚至更糟。但是,C++/CLI 允许您利用所有“脏”优化选项。

于 2009-12-02T08:51:47.907 回答
5

我已经发布了一篇博客文章来解决这个问题。

于 2009-12-21T20:43:54.693 回答
3

关键是C++/CLI。它允许您将 C++ 代码编译成托管的 .NET 程序集。

于 2009-12-02T08:29:52.687 回答
2

今天,制作混合 .Net/native 库以利用这两个平台进行性能优化已成为行业标准。不仅是 NMath,许多带有 .net 接口的商业和免费库都是这样工作的。例如:Math.NET Numerics、dnAnalytics、Extreme Optimization、FinMath等等。与 MKL 的集成在 .net 数值库中非常流行,其中大多数只是使用托管 C++ 程序集作为中间级别。但是这个解决方案有很多缺点:

  1. 英特尔 MKL 是一种专有软件,它有点贵。但是像 dnAnalytics 这样的一些库提供了用纯 .net 代码免费替换 MKL 功能。当然,它要慢得多,但它是免费的并且功能齐全。

  2. 它降低了您需要为 32 位和 64 位模式提供大量托管 C++ 内核 dll 的兼容性。

  3. 托管到本机调用需要执行封送处理,这会降低快速频繁调用操作(如 Gamma 或 NormalCDF)的性能。

最后两个问题在 RTMath FinMath 库中解决。我真的不知道他们是怎么做到的,但他们提供了为任何 CPU 平台编译并支持 32 位和 64 位的单个纯 .net dll。当我需要调用 NormalCDF 数十亿次时,我也没有看到针对 MKL 的任何性能下降。

于 2011-11-04T20:40:52.473 回答
1

由于(本机)英特尔 MKL 正在做数学运算,因此您实际上并没有在托管代码中进行数学运算。您只是使用 .Net 的内存管理器,因此 .Net 代码可以轻松使用结果。

于 2009-12-02T11:23:09.803 回答
0

我从@Darin Dimitrov 对他的回答的评论和@Trevor Misfeldt 对@Darin 的评论的评论中学到了更多。因此,将其发布为答案,以供未来的读者阅读。

NMath 使用 P/Invoke 或 C++/CLI 调用英特尔数学内核库的本地函数,这是进行最密集计算的地方,这也是它如此快速的原因。

时间花在英特尔MKL 内部的分解方法上。也不需要复制数据。所以,这不是 CLI 是否快的问题这是关于执行发生的地方

@Paul 的博客也值得一读。这是摘要。

C# 快,但内存分配不快。 重用变量作为 ref 或 out 参数,而不是从方法中返回新变量。分配新变量会消耗内存并减慢执行速度。@Haymo Kutschbach 已经很好地解释了这一点。

如果不需要精度,从双精度切换到单精度的性能提升是相当可观的(更不用说为数据存储节省内存了)。

对于许多简短的计算,从 C# 调用 C++/cli 例程,将所有指针固定到托管空间中分配的数据,然后调用 Intel 库通常比使用 P/Invoke 直接从 C# 调用库更好,因为封送数据的成本。正如@Haymo Kutschbach 在评论中提到的那样,对于 blittable 类型,C++/CLI 和 C# 之间没有区别。仅包含 blittable 成员的 blittable 类型和类的数组在编组期间被固定而不是复制。请参阅https://msdn.microsoft.com/en-us/library/75dwhxf7(v=vs.110).aspx以获取 blittable 和 non-blittable 类型的列表。

于 2015-12-08T20:16:00.480 回答