7

C# を使用してこれを行うより高速な方法はありますか?

double[,] myArray = new double[length1, length2];

for(int i=0;i<length1;i++)
    for(int j=0;j<length2;j++)
        myArray[i,j] = double.PositiveInfinity;

C++ を使っていたのを覚えています。このようなことを行うために呼ばれるものがありましたmemset()...

4

3 に答える 3

5

多次元配列はメモリの大きなブロックにすぎないため、仕組みと同様に 1 つのように扱うことができますmemset()。これには安全でないコードが必要です。本当にパフォーマンスが重要でない限り、やる価値があるとは言えません。ただし、これは楽しい演習なので、BenchmarkDotNet を使用したベンチマークをいくつか示します。

    public class ArrayFillBenchmark
    {
        const int length1 = 1000;
        const int length2 = 1000;
        readonly double[,] _myArray = new double[length1, length2];

        [Benchmark]
        public void MultidimensionalArrayLoop()
        {
            for (int i = 0; i < length1; i++)
            for (int j = 0; j < length2; j++)
                _myArray[i, j] = double.PositiveInfinity;
        }

        [Benchmark]
        public unsafe void MultidimensionalArrayNaiveUnsafeLoop()
        {
            fixed (double* a = &_myArray[0, 0])
            {
                double* b = a;

                for (int i = 0; i < length1; i++)
                for (int j = 0; j < length2; j++)
                    *b++ = double.PositiveInfinity;
            }
        }

        [Benchmark]
        public unsafe void MultidimensionalSpanFill()
        {
            fixed (double* a = &_myArray[0, 0])
            {
                double* b = a;
                var span = new Span<double>(b, length1 * length2);
                span.Fill(double.PositiveInfinity);
            }
        }

        [Benchmark]
        public unsafe void MultidimensionalSseFill()
        {
            var vectorPositiveInfinity = Vector128.Create(double.PositiveInfinity);

            fixed (double* a = &_myArray[0, 0])
            {
                double* b = a;

                ulong i = 0;
                int size = Vector128<double>.Count;

                ulong length = length1 * length2;
                for (; i < (length & ~(ulong)15); i += 16)
                {
                    Sse2.Store(b+size*0, vectorPositiveInfinity);
                    Sse2.Store(b+size*1, vectorPositiveInfinity);
                    Sse2.Store(b+size*2, vectorPositiveInfinity);
                    Sse2.Store(b+size*3, vectorPositiveInfinity);
                    Sse2.Store(b+size*4, vectorPositiveInfinity);
                    Sse2.Store(b+size*5, vectorPositiveInfinity);
                    Sse2.Store(b+size*6, vectorPositiveInfinity);
                    Sse2.Store(b+size*7, vectorPositiveInfinity);
                    b += size*8;
                }
                for (; i < (length & ~(ulong)7); i += 8)
                {
                    Sse2.Store(b+size*0, vectorPositiveInfinity);
                    Sse2.Store(b+size*1, vectorPositiveInfinity);
                    Sse2.Store(b+size*2, vectorPositiveInfinity);
                    Sse2.Store(b+size*3, vectorPositiveInfinity);
                    b += size*4;
                }
                for (; i < (length & ~(ulong)3); i += 4)
                {
                    Sse2.Store(b+size*0, vectorPositiveInfinity);
                    Sse2.Store(b+size*1, vectorPositiveInfinity);
                    b += size*2;
                }
                for (; i < length; i++)
                {
                    *b++ = double.PositiveInfinity;
                }
            }
        }
    }

結果:

|                               Method |       Mean |     Error |    StdDev | Ratio |
|------------------------------------- |-----------:|----------:|----------:|------:|
|            MultidimensionalArrayLoop | 1,083.1 us | 11.797 us | 11.035 us |  1.00 |
| MultidimensionalArrayNaiveUnsafeLoop |   436.2 us |  8.567 us |  8.414 us |  0.40 |
|             MultidimensionalSpanFill |   321.2 us |  6.404 us | 10.875 us |  0.30 |
|              MultidimensionalSseFill |   231.9 us |  4.616 us | 11.323 us |  0.22 |

MultidimensionalArrayLoop境界チェックのために遅いです。JIT は[i, j]、配列の境界内にあることを確認する各ループのコードを発行します。JIT は境界チェックを省略できる場合がありますが、1 次元配列の場合は省略できます。多次元でそれができるかどうかはわかりません。

MultidimensionalArrayNaiveUnsafeLoop基本的には と同じコードですMultidimensionalArrayLoopが、境界チェックはありません。かなり高速で、40% の時間がかかります。ただし、ループを展開することでループを改善できる可能性があるため、「単純」と見なされます。

MultidimensionalSpanFillも境界チェックを持たず、多かれ少なかれ と同じMultidimensionalArrayNaiveUnsafeLoopですが、Span.Fill内部でループ展開を行うため、単純な unsafe ループよりも少し高速です。オリジナルの30%の時間しかかかりません。

MultidimensionalSseFillループ展開とベクトル化という 2 つのことを行うことで、最初のアンセーフ ループを改善します。これには Sse2 をサポートする CPU が必要ですが、1 つの命令で 128 ビット (16 バイト) を書き込むことができます。これにより、速度がさらに向上し、元の速度の 22% に低下します。興味深いことに、Avx (256 ビット) を使用したこの同じループは、Sse2 バージョンより一貫して遅かったため、ベンチマークはここには含まれていません。

ただし、これらの数値は 1000x1000 のアレイにのみ適用されます。配列のサイズを変更すると、結果が異なります。たとえば、配列サイズを 10000x10000 に変更すると、すべての安全でないベンチマークの結果は非常に近くなります。おそらく、配列が大きいほどメモリ フェッチが多くなるため、最後の 3 つのベンチマークで見られた小さな反復改善が均等化される傾向にあります。

どこかに教訓がありますが、とても楽しい実験だったので、これらの結果を共有したかっただけです.

于 2019-11-29T17:31:25.220 に答える
1
double[,] myArray = new double[x, y];

if( parallel == true )
{
    stopWatch.Start();
    System.Threading.Tasks.Parallel.For( 0, x, i =>
    {
        for( int j = 0; j < y; ++j )
            myArray[i, j] = double.PositiveInfinity;  
    });
    stopWatch.Stop();
    Print( "Elapsed milliseconds: {0}", stopWatch.ElapsedMilliseconds );
}
else
{
    stopWatch.Start();
    for( int i = 0; i < x; ++i )
        for( int j = 0; j < y; ++j )
          myArray[i, j] = double.PositiveInfinity;  
    stopWatch.Stop();
    Print("Elapsed milliseconds: {0}", stopWatch.ElapsedMilliseconds);
}

とを設定するxと、シングルスレッドのアプローチとマルチスレッドのアプローチが得られます。y10000553 milliseconds170

于 2019-11-29T14:31:18.317 に答える