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

时间:2010-05-25 01:47:12

标签: c# .net c performance

我有一个非常大的嵌套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%。

12 个答案:

答案 0 :(得分: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; 
    } 
} 

答案 1 :(得分:7)

使用unsafe块和指针索引到omega数组。这将消除范围检查的开销,如果您进行了足够的访问,则可能是一个重要的胜利。您的GetS()GetC()函数也可能花费大量时间,而这些函数并未提供。

答案 2 :(得分:3)

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

但唯一可以确定的方法是尝试并简介。

答案 3 :(得分: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;
  }
}

答案 4 :(得分:2)

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

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

答案 5 :(得分:2)

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

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

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

答案 6 :(得分:2)

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

答案 7 :(得分:1)

答案 8 :(得分:1)

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

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

答案 9 :(得分:0)

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

考虑到它是一个非常小的功能,你可以对两者进行分析,看看是否有任何差异。

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

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