我知道c#中实例化的值类型数组会自动填充该类型的默认值(例如bool为false, int为0,等等)。
是否有一种方法来自动填充一个不是默认的种子值的数组?无论是在创建或之后的内置方法(如Java的Arrays.fill())?假设我想要一个默认为true的布尔数组,而不是false。是否有一个内置的方法来做到这一点,或者你只需要通过一个for循环迭代数组?
// Example pseudo-code:
bool[] abValues = new[1000000];
Array.Populate(abValues, true);
// Currently how I'm handling this:
bool[] abValues = new[1000000];
for (int i = 0; i < 1000000; i++)
{
abValues[i] = true;
}
必须遍历数组并将每个值“重置”为true似乎效率不高。还有其他方法吗?也许通过翻转所有值?
在输入这个问题并思考之后,我猜默认值只是c#在幕后处理这些对象的内存分配的结果,所以我想这可能是不可能的。但我还是想确定一下!
对于大型数组或可变大小的数组,您可能应该使用:
Enumerable.Repeat(true, 1000000).ToArray();
对于小数组,你可以使用c# 3中的集合初始化语法:
bool[] vals = new bool[]{ false, false, false, false, false, false, false };
集合初始化语法的好处是,不必在每个槽中使用相同的值,可以使用表达式或函数初始化一个槽。另外,我认为您避免了将数组槽初始化为默认值的代价。举个例子:
bool[] vals = new bool[]{ false, true, false, !(a ||b) && c, SomeBoolMethod() };
只是一个基准:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.997 (1909/November2018Update/19H2)
Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.302
[Host] : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
.NET Core 3.1 : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
Job=.NET Core 3.1 Runtime=.NET Core 3.1
| Method | Mean | Error | StdDev |
|----------------- |---------:|----------:|----------:|
| EnumerableRepeat | 2.311 us | 0.0228 us | 0.0213 us |
| NewArrayForEach | 2.007 us | 0.0392 us | 0.0348 us |
| ArrayFill | 2.426 us | 0.0103 us | 0.0092 us |
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.NetCoreApp31)]
public class InitializeArrayBenchmark {
const int ArrayLength = 1600;
[Benchmark]
public double[] EnumerableRepeat() {
return Enumerable.Repeat(double.PositiveInfinity, ArrayLength).ToArray();
}
[Benchmark]
public double[] NewArrayForEach() {
var array = new double[ArrayLength];
for (int i = 0; i < array.Length; i++) {
array[i] = double.PositiveInfinity;
}
return array;
}
[Benchmark]
public double[] ArrayFill() {
var array = new double[ArrayLength];
Array.Fill(array, double.PositiveInfinity);
return array;
}
}
下面的代码结合了小副本的简单迭代和Array。复印大份
public static void Populate<T>( T[] array, int startIndex, int count, T value ) {
if ( array == null ) {
throw new ArgumentNullException( "array" );
}
if ( (uint)startIndex >= array.Length ) {
throw new ArgumentOutOfRangeException( "startIndex", "" );
}
if ( count < 0 || ( (uint)( startIndex + count ) > array.Length ) ) {
throw new ArgumentOutOfRangeException( "count", "" );
}
const int Gap = 16;
int i = startIndex;
if ( count <= Gap * 2 ) {
while ( count > 0 ) {
array[ i ] = value;
count--;
i++;
}
return;
}
int aval = Gap;
count -= Gap;
do {
array[ i ] = value;
i++;
--aval;
} while ( aval > 0 );
aval = Gap;
while ( true ) {
Array.Copy( array, startIndex, array, i, aval );
i += aval;
count -= aval;
aval *= 2;
if ( count <= aval ) {
Array.Copy( array, startIndex, array, i, count );
break;
}
}
}
使用int[]数组的不同数组长度的基准是:
2 Iterate: 1981 Populate: 2845
4 Iterate: 2678 Populate: 3915
8 Iterate: 4026 Populate: 6592
16 Iterate: 6825 Populate: 10269
32 Iterate: 16766 Populate: 18786
64 Iterate: 27120 Populate: 35187
128 Iterate: 49769 Populate: 53133
256 Iterate: 100099 Populate: 71709
512 Iterate: 184722 Populate: 107933
1024 Iterate: 363727 Populate: 126389
2048 Iterate: 710963 Populate: 220152
4096 Iterate: 1419732 Populate: 291860
8192 Iterate: 2854372 Populate: 685834
16384 Iterate: 5703108 Populate: 1444185
32768 Iterate: 11396999 Populate: 3210109
第一列是数组大小,然后是使用简单迭代(@ jaredared实现)复制的时间。此方法的时间在此之后。
这些基准测试使用了一个由四个整数组成的结构数组
2 Iterate: 2473 Populate: 4589
4 Iterate: 3966 Populate: 6081
8 Iterate: 7326 Populate: 9050
16 Iterate: 14606 Populate: 16114
32 Iterate: 29170 Populate: 31473
64 Iterate: 57117 Populate: 52079
128 Iterate: 112927 Populate: 75503
256 Iterate: 226767 Populate: 133276
512 Iterate: 447424 Populate: 165912
1024 Iterate: 890158 Populate: 367087
2048 Iterate: 1786918 Populate: 492909
4096 Iterate: 3570919 Populate: 1623861
8192 Iterate: 7136554 Populate: 2857678
16384 Iterate: 14258354 Populate: 6437759
32768 Iterate: 28351852 Populate: 12843259
我有点惊讶没有人做了非常简单,但超快的SIMD版本:
public static void PopulateSimd<T>(T[] array, T value) where T : struct
{
var vector = new Vector<T>(value);
var i = 0;
var s = Vector<T>.Count;
var l = array.Length & ~(s-1);
for (; i < l; i += s) vector.CopyTo(array, i);
for (; i < array.Length; i++) array[i] = value;
}
基准测试:(数据来自于Framework 4.8,但Core3.1在统计上是相同的)
| Method | N | Mean | Error | StdDev | Ratio | RatioSD |
|----------- |-------- |---------------:|---------------:|--------------:|------:|--------:|
| DarthGizka | 10 | 25.975 ns | 1.2430 ns | 0.1924 ns | 1.00 | 0.00 |
| Simd | 10 | 3.438 ns | 0.4427 ns | 0.0685 ns | 0.13 | 0.00 |
| | | | | | | |
| DarthGizka | 100 | 81.155 ns | 3.8287 ns | 0.2099 ns | 1.00 | 0.00 |
| Simd | 100 | 12.178 ns | 0.4547 ns | 0.0704 ns | 0.15 | 0.00 |
| | | | | | | |
| DarthGizka | 1000 | 201.138 ns | 8.9769 ns | 1.3892 ns | 1.00 | 0.00 |
| Simd | 1000 | 100.397 ns | 4.0965 ns | 0.6339 ns | 0.50 | 0.00 |
| | | | | | | |
| DarthGizka | 10000 | 1,292.660 ns | 38.4965 ns | 5.9574 ns | 1.00 | 0.00 |
| Simd | 10000 | 1,272.819 ns | 68.5148 ns | 10.6027 ns | 0.98 | 0.01 |
| | | | | | | |
| DarthGizka | 100000 | 16,156.106 ns | 366.1133 ns | 56.6564 ns | 1.00 | 0.00 |
| Simd | 100000 | 17,627.879 ns | 1,589.7423 ns | 246.0144 ns | 1.09 | 0.02 |
| | | | | | | |
| DarthGizka | 1000000 | 176,625.870 ns | 32,235.9957 ns | 1,766.9637 ns | 1.00 | 0.00 |
| Simd | 1000000 | 186,812.920 ns | 18,069.1517 ns | 2,796.2212 ns | 1.07 | 0.01 |
可以看到,在小于10000个元素时速度要快得多,超过10000个元素时速度仅略慢。