我最近正在研究C#中的数值算法。因此,我做了一些实验以寻找最适合.NET的数学库。我经常做的一件事是评估目标函数,这些目标函数通常是将向量作为输入并返回向量作为输出的函数。我比较了ILNumerics,系统数组和Math.NET中相同目标函数的实现。 ILNumerics的语法确实使它脱颖而出,因为它对于冗长的数学公式类似于MatLab和R。但是,我发现对于相同数量的评估,ILNumerics似乎比Math.NET的任何系统数组花费的时间都要长。下面是我用来比较的代码。我在这里不做任何线性代数,只是在长矢量上纯粹应用数学公式。

[Test]
public void TestFunctionEval()
{
    int numObj = 2;
    int m = 100000;
    Func<double[], double[]> fun1 = (x) =>
    {
        double[] z = new double[numObj];
        z[0] = x[0];
        double g = 1.0;
        for (int i = 1; i < x.Length; i++)
            g = g + 9.0 * x[i] / (m - 1);
        double h = 1.0 - Math.Sqrt(z[0] / g);
        z[1] = g * h;
        return z;
    };

    Func<ILArray<double>, ILArray<double>> fun2 = (x) =>
    {
        ILArray<double> z = zeros(numObj);
        z[0] = x[0];
        ILArray<double> g = 1.0 + 9.0 * sum(x[r(1, end)]) / (m - 1);
        ILArray<double> h = 1.0 - sqrt(z[0] / g);
        z[1] = g * h;
        return z;
    };

    Func<Vector<double>, Vector<double>> fun3 = (x) =>
    {
        DenseVector z = DenseVector.Create(numObj, (i) => 0);
        z[0] = x[0];
        double g = 1.0 + 9.0*(x.SubVector(1, x.Count - 1) / (m - 1)).Sum();
        double h = 1.0 - Math.Sqrt(z[0] / g);
        z[1] = g * h;
        return z;
    };

    int n = 1000;
    ILArray<double> xs = rand(n, m);
    IList<double[]> xRaw = new List<double[]>();
    for (int i = 0; i < n; i++)
    {
        double[] row = xs[i, full].ToArray();
        xRaw.Add(row);
    }
    DenseMatrix xDen = DenseMatrix.OfRows(n, m, xRaw);
    Stopwatch watch = new Stopwatch();
    watch.Start();
    for (int i = 0; i < n; i++)
    {
        ILArray<double> ret = fun1(xRaw[i]);
    }
    watch.Stop();
    log.InfoFormat("System array took {0} seconds.", watch.Elapsed.TotalSeconds);
    watch.Reset();
    watch.Start();
    for (int i = 0; i < n; i++)
    {
        ILArray<double> ret = fun2(xs[i, full]);
    }
    watch.Stop();
    log.InfoFormat("ILNumerics took {0} seconds.", watch.Elapsed.TotalSeconds);
    watch.Reset();
    watch.Start();
    for (int i = 0; i < n; i++)
    {
        var ret = fun3(xDen.Row(i));
    }
    watch.Stop();
    log.InfoFormat("Math.Net took {0} seconds.", watch.Elapsed.TotalSeconds);
}


不幸的是,该测试表明,ILNumerics花费太长时间才能完成如此简单的操作。

315 | System array took 0.7117623 seconds.
323 | ILNumerics took 14.5100766 seconds.
330 | Math.Net took 5.3917536 seconds.


我真的很喜欢它使代码看起来非常像数学公式的方式。但是,将系统数组或Math.NET用来评估上述函数的时间花费更多时间,这意味着我不得不选择其他替代方法来代替ILNumerics,尽管这将导致更长且更难解释函数。

我是否以错误的方式使用ILNumerics?还是在这种情况下设计较慢?也许我没有将其用于最合适的目的。有人可以解释吗?

测试中使用ILNumerics 3.2.2.0和Math.NET.Numerics 2.6.1.30。

最佳答案

是的,您缺少一些常规的性能测试规则。而且比较也不公平:


对于ILNumerics实现,您创建了很多临时文件,这些临时文件的大小很大。与其他实现相比,这是不利的,在其他实现中,您仅创建一次长向量,并在“内部循环”中进行所有操作。内部循环将始终更快-牺牲表现力较小的语法和更多的编程工作为代价。如果需要这种性能,则始终可以使用x.GetArraysForRead()和x.GetArrayForWrite()直接使用基础System.Array。这为您提供了System.Array测试中的选项...
您在针对ILNumerics的测试中包括了很多子数组创建(和新的内存分配),而其他测试中未包括这些子数组。例如,您从测量循环内的大测试数据矩阵派生子数组。


为什么不这样设计测试:为每个测试分别创建1个大型矩阵。将Mathnet矩阵用于Mathnet测试,将System.Array矩阵用于System.Array测试,将ILArray用于ILNumerics。在每次迭代中,提取相应的行并将其提供给相应的函数。

不要忘记遵循ILNumerics函数规则:http://ilnumerics.net/GeneralRules.html,并在没有任何调试器的情况下使用带有Release版本的测试来运行测试。和往常一样,省去第一次迭代所需的时间。

根据您的系统(及其带来的自动并行化选项),ILNumerics可能仍然较慢。在这种情况下,请考虑遵循ILNumerics的further optimization options或通过诉诸System.Array来优化内部循环。

@Edit:还有一点要注意:您可能已经意识到了这样一个事实,即在没有实际做任何有用的事情的情况下进行此类微测试通常会产生误导。结果可能不适合从中得出对最终应用程序性能的期望。一个示例:如果长时间仅使用System.Array遍历大型数组,则可能最终将大部分时间花在GC而不是数字上。您将必须谨慎,不要重新分配任何存储空间,这会使您的代码更加笨拙。

ILNumerics(如果使用正确)可以通过自动重用内存来避免在GC上花费时间。而且,它在内部并行化算法(即使像示例中那样,仅使用向量对并行化的要求也不高)。

关于c# - 向量与系统数组和Math.NET上的简单数学运算的ILNumerics的性能,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/19224626/

10-09 09:29