.NET在数值计算中的速度

时间:2009-12-02 08:09:30

标签: c# .net managed-c++ managed-code nmath

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

我已经在stackoverflow上跟踪了广告 http://www.centerspace.net/products/

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

  

Q值。 NMath是“纯粹的”.NET吗?

     

一个。答案在某种程度上取决于您对“纯.NET”的定义。 NMath是用C#编写的,加上一个小的Managed C ++层。但是,为了更好地执行基本线性代数运算,NMath确实依赖于本机Intel Math Kernel Library(包含在NMath中)。但是没有COM组件,没有DLL - 只是.NET程序集。此外,在托管C ++层中分配并由本机代码使用的所有内存都从托管堆中分配。

有人可以向我解释一下吗?

8 个答案:

答案 0 :(得分:11)

  

他们怎么能这样做?

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

F#.NET期刊文章Numerical Libraries: special functions, interpolation and random numbers(2008年3月16日)和Numerical Libraries: linear algebra and spectral methods(2008年4月16日)测试了相当多的功能,而NMath实际上是所有商业图书馆中最慢的。他们的PRNG比其他所有人慢,比免费的Math.NET库慢50%,缺少一些基本功能(例如计算Gamma(-0.5)的能力)和其他基本功能(他们提供的Gamma相关功能)坏了。 Extreme Optimization和Bluebit都在eigensolver基准测试中击败了NMath。 NMath当时甚至没有提供傅立叶变换。

更令人惊讶的是,性能差异有时是巨大的。我们测试的最昂贵的商业数字库(IMSL)比FFT基准测试中的免费FFTW库慢500多倍,并且库的 none 当时使用了多个内核。

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

答案 1 :(得分:10)

我是ILNumerics的主要开发人员之一。所以我很有偏见,很明显;)但是我们对内部的更多披露,所以我会对我们的速度'秘密'提供一些见解。

这完全取决于系统资源的使用方式!如果你是关于纯粹的速度并需要处理大型数组,你将确保(按重要性排序,最重要的是首先)

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

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

  3. 要像{FORTRAN和C ++一样实现speed of highly optimized languages,必须将相同的优化应用到您的代码中。 C#提供了这样做的选项。

  4. 请注意,这些注意事项应该按顺序进行!如果瓶颈是内存带宽并且处理器花费大部分时间等待新数据,那么关心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并发布比较结果,我相信,此处列出的其他库将遵循。 (?)

答案 2 :(得分:8)

关于C ++ / CLI的观点是正确的。要完成图片,只需另外两点:

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

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

答案 3 :(得分:5)

我发布了一个解决此问题的博客article

答案 4 :(得分:3)

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

答案 5 :(得分:2)

今天,制作混合的.Net /本地库是行业标准,以便利用这两个平台进行性能优化。不仅是NMath,还有许多带有.net接口的商业和免费图书馆。例如:Math.NET Numerics,dnAnalytics,极限优化,FinMath等等。与MKL的集成在.net数值库中非常流行,并且大多数仅使用托管C ++程序集作为中间级别。但是这个解决方案有许多缺点:

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

  2. 它会降低您在32位和64位模式下拥有大量托管C ++内核dll所需的兼容性。

  3. 管理到原生呼叫需要执行编组,这会降低快速调用的快速操作(如Gamma或NormalCDF)的性能。

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

答案 6 :(得分:1)

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

答案 7 :(得分:0)

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

NMath使用P / Invoke或C ++ / CLI来调用Intel Math Kernel Library本机函数,这是进行最密集计算的地方,也就是它如此之快的原因。

在英特尔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类型的列表。