3

我最近正在研究 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。

4

2 回答 2

5

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

  1. 对于 ILNumerics 实现,您创建了许多相当大的临时对象。与其他实现相比,这是不利的,在其他实现中,您只创建一次长向量并在“内部循环”中执行所有操作。内部循环总是会更快——代价是表达性较差的语法和更多的编程工作。如果您需要这种性能,您始终可以使用 x.GetArraysForRead() 和 x.GetArrayForWrite() 直接使用底层 System.Array。这为您提供 System.Array 测试中的选项...

  2. 您在 ILNumerics 的测试中包含了许多子数组创建(和新的内存分配),而其他测试中不包含这些子数组。例如,您从测量循环内的大测试数据矩阵中导出子数组。

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

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

根据您的系统(以及它带来的自动并行化选项),ILNumerics 可能仍然较慢。在这种情况下,请考虑遵循ILNumerics 的进一步优化选项或通过使用 System.Array 来优化内部循环。

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

ILNumerics - 如果使用正确 - 通过自动重用内存来防止您在 GC 中花费时间。此外,它会在内部并行化您的算法(即使仅使用向量对并行化的要求还不够,如您的示例所示)。

于 2013-10-07T13:18:36.830 回答
4

将测试更改为以下内容,并且 ILNumerics 执行得更快。:

        [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<ILInArray<double>, ILRetArray<double>> fun2 = (xIn) =>
            {
                using (ILScope.Enter(xIn))
                {
                    ILArray<double> x = xIn;
                    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, m - 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);

            int numTest = 10;

            for (int k = 0; k < numTest; k++)
            {
                log.InfoFormat("Round {0}.", k);
                Stopwatch watch = new Stopwatch();
                watch.Reset();
                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]);
                    ILArray<double> ret = fun2(xRaw[i]);
                }
                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));
                    var ret = fun3(DenseVector.OfEnumerable(xRaw[i]));
                }
                watch.Stop();
                log.InfoFormat("Math.Net took {0} seconds.", watch.Elapsed.TotalSeconds);
            }

 NumericsTest   318      Round 0.
 NumericsTest   327      System array took 0.7008772 seconds.
 NumericsTest   336      ILNumerics took 1.9559407 seconds.
 NumericsTest   315      Math.Net took 5.2027841 seconds.
 NumericsTest   318      Round 1.
 NumericsTest   327      System array took 0.6791225 seconds.
 NumericsTest   336      ILNumerics took 0.4739782 seconds.
 NumericsTest   315      Math.Net took 4.931067 seconds.
 NumericsTest   318      Round 2.
 NumericsTest   327      System array took 0.6734302 seconds.
 NumericsTest   336      ILNumerics took 0.470311 seconds.
 NumericsTest   315      Math.Net took 4.8086843 seconds.
 NumericsTest   318      Round 3.
 NumericsTest   327      System array took 0.6801929 seconds.
 NumericsTest   336      ILNumerics took 0.471479 seconds.
 NumericsTest   315      Math.Net took 4.8423348 seconds.
 NumericsTest   318      Round 4.
 NumericsTest   327      System array took 0.6761803 seconds.
 NumericsTest   336      ILNumerics took 0.4709513 seconds.
 NumericsTest   315      Math.Net took 4.7920563 seconds.
 NumericsTest   318      Round 5.
 NumericsTest   327      System array took 0.6820961 seconds.
 NumericsTest   336      ILNumerics took 0.471545 seconds.
 NumericsTest   315      Math.Net took 4.7798939 seconds.
 NumericsTest   318      Round 6.
 NumericsTest   327      System array took 0.6779479 seconds.
 NumericsTest   336      ILNumerics took 0.4862169 seconds.
 NumericsTest   315      Math.Net took 4.5421089 seconds.
 NumericsTest   318      Round 7.
 NumericsTest   327      System array took 0.6760993 seconds.
 NumericsTest   336      ILNumerics took 0.4704415 seconds.
 NumericsTest   315      Math.Net took 4.8233003 seconds.
 NumericsTest   318      Round 8.
 NumericsTest   327      System array took 0.6759367 seconds.
 NumericsTest   336      ILNumerics took 0.4710648 seconds.
 NumericsTest   315      Math.Net took 4.7945989 seconds.
 NumericsTest   318      Round 9.
 NumericsTest   327      System array took 0.6761679 seconds.
 NumericsTest   336      ILNumerics took 0.4779321 seconds.
 NumericsTest   315      Math.Net took 4.7426801 seconds.
于 2013-10-07T17:48:52.820 回答