使用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()
的东西来做这些事......
答案 0 :(得分:2)
多维数组只是一个很大的内存块,因此我们可以像对待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有时可以跳过边界检查,我知道它对一维数组也是如此。我不确定它是否适用于多维。
MultidimensionalArrayNaiveUnsafeLoop
本质上与MultidimensionalArrayLoop
相同,但是没有边界检查。它的速度相当快,花费了40%的时间。不过,它被认为是“天真”的,因为通过展开循环仍然可以改善循环。
MultidimensionalSpanFill
也没有边界检查,并且与MultidimensionalArrayNaiveUnsafeLoop
大致相同,但是Span.Fill
在内部确实会展开循环,这就是为什么它比我们幼稚的不安全循环。只需30%的时间即可恢复原样。
MultidimensionalSseFill
通过做两件事改进了我们的第一个不安全循环:循环展开和向量化。这需要具有Sse2支持的CPU,但是它允许我们在一条指令中写入128位(16字节)。这给我们带来了额外的速度提升,使其降至原始速度的22%。有趣的是,使用Avx(256位)进行的同一循环始终比Sse2版本慢,因此此处不包括基准测试。
但是这些数字仅适用于1000x1000的数组。当您更改数组的大小时,结果会有所不同。例如,当我们将数组大小更改为10000x10000时,所有不安全基准的结果都非常接近。可能是因为较大的数组有更多的内存获取,因此它倾向于使最近三个基准测试中看到的较小的迭代改进等同。
某处有一堂课,但是我主要只是想分享这些结果,因为这是一个非常有趣的实验。
答案 1 :(得分:1)
我写的方法并不快,但是它可以用于实际的多维数组,而不仅仅是二维数组。
public static class ArrayExtensions
{
public static void Fill(this Array array, object value)
{
var indicies = new int[array.Rank];
Fill(array, 0, indicies, value);
}
public static void Fill(Array array, int dimension, int[] indicies, object value)
{
if (dimension < array.Rank)
{
for (int i = array.GetLowerBound(dimension); i <= array.GetUpperBound(dimension); i++)
{
indicies[dimension] = i;
Fill(array, dimension + 1, indicies, value);
}
}
else
array.SetValue(value, indicies);
}
}
答案 2 :(得分: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
和y
设置为10000
时,单线程方法得到553 milliseconds
,多线程方法得到170
。