我有一个C#应用程序调用Fortran DLL来进行数值计算。已经确定DLL调用比从Fortran控制台应用程序调用相同的计算慢至少10倍,所以我已经开始将代码移植到C#。移植过程是复制代码并逐行修复语法。所以C#和Fortran看起来非常相似。 Fortran中的常见块成为类中的字段。在移植了一些核心例程之后,我开始测试并发现双精度C#代码比双精度Fortran慢30倍(比单精度Fortran慢50倍)。我遍历我的测试代码100x以最小化C#JIT编译器开销的贡献。
代码使用复杂的算术和复杂函数,如SQRT和LOG10。我提供了一个C#结构来处理这个数学。我怀疑这就是问题所在,但VS2010 Pro似乎没有一个分析器,所以我不确定。
Fortran编译是Intel的最新版本。我没有对C#或Fortran代码进行任何特殊优化。我使用发布版本比较了时间。我只有一个两核CPU,所以并行可能不会有太多帮助。
我可以就如何加快此代码使用一些建议。
这是我的一种方法。 C#看起来就像Fortran,我没写过。
public void Forwx(double rshldr, double rbed, double[][] resdep, double[]toolparm)
{
var zex = new Complex[70];
var zey = new Complex[70];
var px2 = new Complex[70];
var px4 = new Complex[70];
var rlt = new double[2];
var trsmt = new double[2];
var fr = new double[2];
var dnc = -0.02;
var factr = 26332.65;
var rh2 = Math.Max(0.1, rbed);
var rh1 = Math.Max(0.1, rshldr);
const double e1 = 1.0;
const double e2 = 1.0;
const double er = 0.818730753077982;
const double re = 1.0 / er;
var ii = Complex.I;
const double pi = Math.PI;
const double eps0 = 8.854e-12;
const double amu0 = 4.0e-7 * pi;
for (var ktool = 3; ktool <= 6; ktool++)
{
if (ktool == 3) // Integrated 2MHz
{
dnc = -0.02;
rlt[0] = 0.2794;
rlt[1] = -0.2794;
trsmt[0] = 0.904875;
trsmt[1] = -0.904875;
fr[0] = 2000.0;
factr = 26332.65;
}
if (ktool == 4) // Integrated 400kHz
{
dnc = -0.02;
rlt[0] = 0.2794;
rlt[1] = -0.2794;
trsmt[0] = 0.904875;
trsmt[1] = -0.904875;
fr[0] = 400.0;
factr = 26811.866;
}
if (ktool == 5) // Option 5 20kHz
{
dnc = -0.1;
rlt[0] = 0.0;
rlt[1] = 0.0;
trsmt[0] = 5.75;
trsmt[1] = 5.75;
fr[0] = 20.0;
factr = 26811.866 * 2.516 * toolparm[1] / 0.28e8;
}
if (ktool == 6) // Option 6 50kHz
{
dnc = -0.1;
rlt[0] = 0.0;
rlt[1] = 0.0;
trsmt[0] = 5.75;
trsmt[1] = 5.75;
fr[0] = 50.0;
factr = 26811.866 * 6.291 * toolparm[2] / 0.7e8;
}
var r1 = trsmt[0] - rlt[0];
var r2 = trsmt[0] - rlt[1];
var omega = 2000.0 * pi * fr[0];
var k12 = omega*amu0*(omega*e1*eps0 + ii/rh1);
var k22 = omega*amu0*(omega*e2*eps0 + ii/rh2);
var krat = (k22 - k12)/k12;
for (var iz = 0; iz < 601; iz++)
{
var recx1 = new Complex(0.0, 0.0);
var rx1 = new Complex(0.0, 0.0);
var recy1 = new Complex(0.0, 0.0);
var ry1 = new Complex(0.0, 0.0);
var lam = new Complex(3.01517934056e-04 / (Math.Pow(er, 5) * r1));
Complex c1;
Complex c2;
for (var i = 0; i < 70; i++)
{
if (iz == 0)
{
lam = lam * re;
var lam2 = lam * lam;
var p11 = lam2 - k12;
var p1 = Complex.Sqrt(p11);
var p22 = lam2 - k22;
var p2 = Complex.Sqrt(p22);
zex[i] = Complex.Exp(dnc * p2);
zey[i] = Complex.Exp(dnc * p1);
c1 = p2 * k12;
c2 = p1 * k22;
var t3 = lam / p2;
var t2 = t3 * (c1 - c2) / (c1 + c2);
var q2 = lam * krat * (t2 + t3) / (p1 + p2);
px2[i] = (lam2 * q2 + lam * p2 * t2);
px4[i] = px2[i];
}
else
{
px2[i] = px2[i] * zex[i];
px4[i] = px4[i] * zey[i];
}
recx1 = recx1 + a1[i] * px2[i];
recy1 = recy1 + a1[i] * px4[i];
rx1 = rx1 + px2[i] * as1i[i];
ry1 = ry1 + px4[i] * as1i[i];
}
if (ktool <= 4)
{
c1 = recx1*r1;
c2 = rx1*r2;
c2 = c2 - Math.Pow(r1/r2,3)*c1;
resdep[12 - ktool][iz + 600] = c2.Re*factr;
c1 = recy1*r1;
c2 = ry1*r2;
c2 = c2 - Math.Pow(r1 / r2,3) * c1;
resdep[12 - ktool][600 - iz] = c2.Re*factr;
}
else
{
c1 = recx1*r1;
//c2 = rx1*r2;
//c2 = c2 - Math.Pow(r1 / r2,3) * c1;
resdep[ktool + 5][iz + 600] = c1.Re * factr;
c1 = recy1*r1;
//c2 = ry1*r2;
//c2 = c2 - Math.Pow(r1 / r2,3) * c1;
resdep[ktool + 5][600 - iz] = c1.Re * factr;
}
}
}
}
以下是复杂结构中的一些方法。
public static Complex Sqrt(double x)
{
return x >= 0 ? new Complex(Math.Sqrt(x)) : new Complex(0, Math.Sqrt(-x));
}
public static Complex Exp(Complex z)
{
return new Complex(Math.Exp(z.Re) * Math.Cos(z.Im), Math.Exp(z.Re) * Math.Sin(z.Im));
}
public static Complex Log(Complex z)
{
return new Complex(Math.Log(Abs(z)), Arg(z));
}
这是复杂结构的一部分。
public struct Complex
{
private readonly double _re;
private readonly double _im;
#region Properties
public double Re
{
get { return _re; }
//set { re = value; }
}
public double Im
{
get { return _im; }
//set { im = value; }
}
public static Complex I
{
get { return new Complex(0.0, 1.0); }
}
public static Complex Zero
{
get { return new Complex(0.0, 0.0); }
}
public static Complex One
{
get { return new Complex(1.0, 0.0); }
}
#endregion
#region constructors
public Complex(double x)
{
_re = x;
_im = 0.0;
}
public Complex(Complex z)
{
_re = z.Re;
_im = z.Im;
}
public Complex(double x, double y) //constructor
{
_re = x;
_im = y;
}
#endregion
}
答案 0 :(得分:3)
您应该尝试删除Complex
结构,而不是使用the built-in结构。它位于System.Numerics
命名空间中。您可能需要对代码执行查找和替换,以将Complex.I
行替换为Complex.ImaginaryOne
,但这应该是一个相当简单的转换。
这有两个好处:
1)内置逻辑将比你能写的任何东西都更好地优化(或者至少不会更差) 2)它使得易于维护,因为它使用.NET标准,所以任何人都可以查看文档,以及任何可以对代码起作用的增强功能。
答案 1 :(得分:1)
我看到的最佳途径是使用C ++ / CLI和AMP来利用GPU进行繁重的计算。
但在此之前,请确保性能问题与DLL有关,而不是数据编组等...
答案 2 :(得分:0)
我计算了我的算法,复数计算被双重操作所取代,并且速度提高了4倍。当然答案是错误的,但是我没有代码的基线而没有复杂运算符调用的开销。如果我能弄清楚如何内联复杂数学,这应该与代码可以获得的速度一样快。 4倍的因子仍然比同等的Fortran慢得多。
所以最后的答案是它无法完成。对于严重的数字运算,在时间很重要的情况下,C#不提供答案。我相信必须坚持使用本机Fortran或C ++。
我感谢大家提高C#数字速度的技巧。