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()
...
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()
...
多次元配列はメモリの大きなブロックにすぎないため、仕組みと同様に 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 つのベンチマークで見られた小さな反復改善が均等化される傾向にあります。
どこかに教訓がありますが、とても楽しい実験だったので、これらの結果を共有したかっただけです.
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
と、シングルスレッドのアプローチとマルチスレッドのアプローチが得られます。y
10000
553 milliseconds
170