3

I am recently doing some studies on numerical algorithms in C#. Therefore I did some experiments in search for the most suitable math library for .NET. One thing I do very often is to evaluate objective functions, which usually are functions that take a vector as input and returns a vector as output. I compared the implementations of the same objective function in ILNumerics, system array and Math.NET. The syntax of ILNumerics really makes it stand out because it resembles that of MatLab and R for lengthy mathematical formulas. However, I discovered that for the same number of evaluations, ILNumerics seems to be taking much longer than either system array of Math.NET. Below is the code I used to compare. I'm not doing any linear algebra here, just purely applying math formulas over long vectors.

[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);
}

Unfortunately, the test shows that ILNumerics is taking too long to do something so simple.

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

I really liked the way it made the code look so much like mathematical formulas. However, taking many more times the time taken by system array or Math.NET to evaluate functions such as the above means I have to choose other alternatives instead of ILNumerics even though this will lead to longer and harder to interpret functions.

Am I using ILNumerics in the wrong way? Or is it by design slower in this kind of scenarios. Maybe I'm not using it for the most suitable purpose. Can someone explain?

ILNumerics 3.2.2.0 and Math.NET.Numerics 2.6.1.30 are used in the test.

4

2 に答える 2

5

はい、いくつかの一般的なパフォーマンス テスト ルールがありません。また、比較も公平ではありません。

  1. ILNumerics の実装では、かなりのサイズの一時変数を多数作成します。これは、長いベクトルを一度だけ作成し、すべての操作を「内部ループ」で実行する他の実装と比較して不利です。内側のループは常に高速になりますが、表現力の低い構文とより多くのプログラミング作業が犠牲になります。そのパフォーマンスが必要な場合は、いつでも x.GetArraysForRead() と x.GetArrayForWrite() を使用して、基になる System.Array を直接使用できます。これにより、 System.Array テストからオプションが取得されます...

  2. 他のテストには含まれていない ILNumerics のテストに、多くのサブ配列の作成 (および新しいメモリ割り当て) を含めます。たとえば、測定ループ内の大きなテスト データ マトリックスからサブアレイを導き出します。

テストをそのように設計しない理由: すべてのテストに対して 1 つの大きなマトリックスを個別に作成します。Mathnet テストには Mathnet 行列、System.Array テストには System.Array 行列、ILNumerics には ILArray を使用します。すべての反復で、対応する行を抽出し、対応する関数に渡します。

ILNumerics 関数のルール ( http://ilnumerics.net/GeneralRules.html ) に従い、デバッガーを接続せずにリリース ビルドでテストを実行することを忘れないでください。いつものように、最初の反復に必要な時間は省略します。

システム (およびそれがもたらす自動並列化オプション) によっては、ILNumerics がさらに遅くなる可能性があります。その場合は、ILNumerics のさらなる最適化オプションに従うか、System.Array に頼って内側のループを最適化することを検討してください。

@編集:もう1つの注意:おそらく、実際に何も有用なことをせずにそのようなマイクロテストを行うと、常に誤解を招くという事実に気付いているでしょう。結果は、最終的なアプリケーションのパフォーマンスに対する期待を導き出すのに適していない可能性があります。1 つの例: 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 に答える