通过编写C dll来加速C#中的数学代码?

问题描述 投票:12回答:12

我有一个非常大的嵌套for循环,其中对浮点数执行一些乘法和加法。

for (int i = 0; i < length1; i++)
{
    double aa = 0;
    for(int h = 0; h < 10; h++)
    {
       aa += omega[i][outsideGeneratedAddress[h]];
    }

    double alphaOld = alpha;
    alpha = Math.Sqrt(alpha * alpha + aa * aa);

    s = -aa / alpha;
    c = alphaOld / alpha;

    for(int j = 0; j <= i; j++)
    {
        double oldU = u[j];
        u[j] = c * oldU + s * omega[i][j];
        omega[i][j] = c * omega[i][j] - s * oldU;
    }
}

这个循环占用了我的大部分处理时间,并且是一个瓶颈。

如果我在C中重写这个循环并从C#接口,我是否可能看到任何速度改进?

编辑:我更新了代码,以显示如何生成s和c。内部循环实际上从0到i,尽管它可能对问题没有太大影响

EDIT2:我在VC ++中实现了算法,并通过dll将其与C#相关联,并且在启用所有优化后,C#的速度提升了28%。启用S​​SE2的参数特别有效。使用MinGW和gcc4.4进行编译只能提高15%的速度。刚尝试了英特尔编译器,该代码的速度提升了49%。

c# .net c performance
12个回答
2
投票

虽然大多数其他答案倾向于建议你研究C#解决方案,但大多数人都错过了一点:如果你使用一个好的优化编译器,这个方法的C代码会更快(我建议英特尔,这种代码非常适合这种代码) )。 编译器还将从JIT中节省一些工作,并将产生更好的编译输出(即使MSVC编译器也可以生成SSE2指令)。默认情况下不会检查数组边界,可能会有一些循环展开 - 总而言之 - 您可能会看到显着的性能提升。 正如已经正确指出的那样,调用本机代码可能会产生一些开销;但是,如果length1足够大,那么与加速相比,这应该是微不足道的。 您可以确保将此代码保留在C#中,但请记住,与几个C编译器相比,CLR(与我所知的所有其他VM一样)对优化生成的代码几乎没有作用。


0
投票

非常怀疑它。处理原始类型并且不分配内存的内部循环在C#中非常有效。本地字节码将从IL生成一次,因此不应该有很多管理开销。

考虑到它是一个非常小的功能,你可以分析两者,看看是否有任何区别。


0
投票

还要考虑在托管和本机调用之间编组数据的成本。 C#的执行速度非常快。您还可以NGEN程序集生成程序集的本机映像,以便更快地执行。


0
投票

我不知道这有多实用,但您是否想过尝试在GPU上运行它?也许使用像OpenCL或DirectCompute这样的东西?

依赖项和平方根可能会扼杀你,但是现在GPU的原始浮点性能比CPU高出一个数量级。


8
投票

更新:

如果你编写内部循环来考虑引用的位置会发生什么:

for (int i = 0; i < length1; i++) 
{ 
    s = GetS(i); 
    c = GetC(i); 
    double[] omegaTemp = omega[i]; 

    for(int j = 0; j < length2; j++) 
    { 
        double oldU = u[j]; 
        u[j] = c * oldU + s * omegaTemp[j]; 
        omegaTemp[j] = c * omegaTemp[j] - s * oldU; 
    } 
} 

7
投票

使用unsafe块和指针来索引你的omega数组。这将消除范围检查的开销,如果您进行了足够的访问,则可能是一个重要的胜利。很多时间也可能花在你的GetS()GetC()函数上,你没有提供源代码。


3
投票

在本机C / C ++中运行它不太可能“自动”加速。如果你对SIMD很好(并且length1length2足够大以至于P / Invoke调用不重要)那么也许你可以做点什么。

但唯一可以确定的方法是尝试和分析。


3
投票

您可以尝试使用Mono.Simd来更有效地利用CPU。

http://tirania.org/blog/archive/2008/Nov-03.html

话虽如此,通过手动从循环中提取重复语句,可以在C#中获得更多。

var outsideAddr0 = outsideGeneratedAddress[0];
var outsideAddr1 = outsideGeneratedAddress[1];
var outsideAddr2 = outsideGeneratedAddress[2];
var outsideAddr3 = outsideGeneratedAddress[3];
var outsideAddr4 = outsideGeneratedAddress[4];
var outsideAddr5 = outsideGeneratedAddress[5];
var outsideAddr6 = outsideGeneratedAddress[6];
var outsideAddr7 = outsideGeneratedAddress[7];
var outsideAddr8 = outsideGeneratedAddress[8];
var outsideAddr9 = outsideGeneratedAddress[9];
for (int i = 0; i < length1; i++)
{
  var omegaAtI = omega[i];
  double aa = 
   omegaAtI[outsideAddr0]
   + omegaAtI[outsideAddr1]
   + omegaAtI[outsideAddr2]
   + omegaAtI[outsideAddr3]
   + omegaAtI[outsideAddr4]
   + omegaAtI[outsideAddr5]
   + omegaAtI[outsideAddr6]
   + omegaAtI[outsideAddr7]
   + omegaAtI[outsideAddr8]
   + omegaAtI[outsideAddr9];

  double alphaOld = alpha;
  alpha = Math.Sqrt(alpha * alpha + aa * aa);

  var s = -aa / alpha;
  var c = alphaOld / alpha;

  for(int j = 0; j <= i; j++)
  {
    double oldU = u[j];
    var omegaAtIJ = omegaAtI[j];
    u[j] = c * oldU + s * omegaAtIJ;
    omegaAtI[j] = c * omegaAtIJ  - s * oldU;
  }
}

2
投票

.net与非托管代码互操作非常慢。只需使用system api分配非托管内存,就可以使用非托管内存的所有好处。

您可以调用VirtualAlloc来分配内存页,然后调用VirtualProtect将它们直接固定到RAM而无需交换。

这种方法允许对大量数据执行计算的速度至少比在托管内存中执行快3倍。


2
投票

简单地使用C或C ++不会给你太多的速度提升,你也需要进行优化。你也有调用C例程的开销,而不是一个巨大的影响,除非你在循环中多次这样做。

首先在C#中尝试一些其他的东西。如果变量是浮点而不是双精度,则会减慢计算速度。同样,Raj表示使用并行编程将为您带来巨大的速度提升。



1
投票

对于Java中的普通64位算术,当将其移植到C并摆弄优化标志(-fprofile-generate,-fprofile-use)时,我看到了大约33%的加速(23 ns到16 ns)。这可能是值得的。

另一件事是omega [i] [j]看起来像omega是一个数组数组 - 你可以用二维数组获得更好的性能(我认为语法类似于omega [i,j],但是我忘记你如何分配一个)。

© www.soinside.com 2019 - 2024. All rights reserved.