这是Microsoft遗弃给我们的Framework用户的另一个版本。它是Panos Theof的解决方案以及Eric J和Petar Petrov的并行解决方案的四倍Array.Clear
和更快的速度。 -对于大型阵列,速度是其两倍。
首先,我想向您介绍函数的祖先,因为这样可以更轻松地理解代码。在性能方面,这几乎与Panos Theof的代码相当,并且对于某些事情可能已经足够了:
public static void Fill<T> (T[] array, int count, T value, int threshold = 32)
{
if (threshold <= 0)
throw new ArgumentException("threshold");
int current_size = 0, keep_looping_up_to = Math.Min(count, threshold);
while (current_size < keep_looping_up_to)
array[current_size++] = value;
for (int at_least_half = (count + 1) >> 1; current_size < at_least_half; current_size <<= 1)
Array.Copy(array, 0, array, current_size, current_size);
Array.Copy(array, 0, array, current_size, count - current_size);
}
如您所见,这是基于已初始化部分的重复加倍。这是简单而有效的,但与现代内存体系结构不符。因此诞生了一个版本,该版本仅使用加倍来创建对缓存友好的种子块,然后将该块迭代遍历目标区域:
const int ARRAY_COPY_THRESHOLD = 32; // 16 ... 64 work equally well for all tested constellations
const int L1_CACHE_SIZE = 1 << 15;
public static void Fill<T> (T[] array, int count, T value, int element_size)
{
int current_size = 0, keep_looping_up_to = Math.Min(count, ARRAY_COPY_THRESHOLD);
while (current_size < keep_looping_up_to)
array[current_size++] = value;
int block_size = L1_CACHE_SIZE / element_size / 2;
int keep_doubling_up_to = Math.Min(block_size, count >> 1);
for ( ; current_size < keep_doubling_up_to; current_size <<= 1)
Array.Copy(array, 0, array, current_size, current_size);
for (int enough = count - block_size; current_size < enough; current_size += block_size)
Array.Copy(array, 0, array, current_size, block_size);
Array.Copy(array, 0, array, current_size, count - current_size);
}
注意:需要使用较早的代码(count + 1) >> 1
作为加倍循环的限制,以确保最终的复制操作具有足够的草料以覆盖所有剩余的内容。如果count >> 1
要使用奇数计数,则不是这种情况。对于当前版本,这无关紧要,因为线性复制循环会吸收任何松弛。
数组单元的大小必须作为参数传递,因为-令人费解的是- sizeof
除非泛型使用unmanaged
了将来可能会或可能不再可用的约束(),否则不允许使用。错误的估计并不重要,但如果值准确,则性能最好,原因如下:
这是我的代码Array.Clear
和前面提到的其他三个解决方案的基准。时序用于填充Int32[]
给定大小的整数数组()。为了减少由高速缓存可变等导致的变化,每个测试被连续执行了两次,并为第二次执行计时。
array size Array.Clear Eric J. Panos Theof Petar Petrov Darth Gizka
-------------------------------------------------------------------------------
1000: 0,7 µs 0,2 µs 0,2 µs 6,8 µs 0,2 µs
10000: 8,0 µs 1,4 µs 1,2 µs 7,8 µs 0,9 µs
100000: 72,4 µs 12,4 µs 8,2 µs 33,6 µs 7,5 µs
1000000: 652,9 µs 135,8 µs 101,6 µs 197,7 µs 71,6 µs
10000000: 7182,6 µs 4174,9 µs 5193,3 µs 3691,5 µs 1658,1 µs
100000000: 67142,3 µs 44853,3 µs 51372,5 µs 35195,5 µs 16585,1 µs
如果此代码的性能不足,那么将有可能使线性复制循环(所有线程使用相同的源块)或我们的好朋友P / Invoke并行化。
注意:清除和填充块通常是通过运行时例程完成的,这些例程使用MMX / SSE指令分支为高度专业化的代码,而在任何体面的环境中,它们都只是简单地称呼各自的道德等值std::memset
并确保达到专业的性能水平。IOW,按权利,库函数Array.Clear
应该将我们所有的手动版本都抛在脑后。相反,它的事实说明了事情的真实程度。Fill<>
首先,必须自己动手,因为它仍然仅存在于Core和Standard中,而不存在于Framework中。.NET已经存在了将近20年了,我们仍然必须左右P / Invoke以获得最基本的东西,或者自己开发。