对非常大的数组进行随机访问是否有任何可能的优化(我目前使用 uint8_t
,我在问什么更好)
uint8_t MyArray[10000000];
当数组中任意位置的值为
- 95% 的情况为 0 或 1 ,
- 2 在 4% 的情况下,
- 在其他 1% 的情况下,在 3 到 255 之间?
那么,有什么比 uint8_t
数组更好的呢?应该尽可能快地以随机顺序循环整个阵列,这对 RAM 带宽来说非常沉重,因此当有多个线程同时为不同的阵列执行此操作时,当前整个 RAM 带宽很快就饱和了。
我问是因为拥有如此大的数组(10 MB)感觉非常低效,而实际上除了 5% 之外,几乎所有值都是 0 或 1。所以当数组中所有值的 95% 时实际上只需要 1 位而不是 8 位,这将减少内存使用量几乎一个数量级。感觉必须有一个内存效率更高的解决方案,这将大大减少所需的 RAM 带宽,因此随机访问也明显更快。
原文由 JohnAl 发布,翻译遵循 CC BY-SA 4.0 许可协议
想到的一个简单的可能性是为常见情况保留每个值 2 位的压缩数组,每个值保留 4 个字节(原始元素索引为 24 位,实际值为 8 位,所以
(idx << 8) | value)
) 其他数组的排序数组。查找值时,首先在 2bpp 数组中查找 (O(1));如果您找到 0、1 或 2,这就是您想要的值;如果你找到 3 这意味着你必须在辅助数组中查找它。在这里,您将执行二进制搜索以查找您感兴趣的 索引 左移 8(O(log(n),n 较小,因为这应该是 1%),并从 4-字节的东西。
对于您建议的数组,第一个数组需要 10000000 / 4 = 2500000 字节,第二个数组需要 10000000 * 1% * 4 B = 400000 字节;因此 2900000 字节,即小于原始数组的三分之一,并且最常用的部分都保存在内存中,这应该有利于缓存(它甚至可能适合 L3)。
如果您需要超过 24 位寻址,则必须调整“辅助存储”;扩展它的一种简单方法是使用 256 元素指针数组来切换索引的前 8 位并转发到如上所述的 24 位索引排序数组。
快速基准测试
(代码和数据总是在我的 Bitbucket 中更新)
上面的代码填充了一个 10M 元素数组,其中随机数据分布为他们帖子中指定的 OP,初始化我的数据结构,然后:
(请注意,在顺序查找的情况下,数组总是以巨大的优势获胜,因为它是您可以做的最适合缓存的查找)
最后两个块重复 50 次并计时;最后,计算和打印每种查找类型的平均值和标准差,以及加速比(lookup_mean/array_mean)。
我在 Ubuntu 16.04 上使用 g++ 5.4.0(
-O3 -static
,加上一些警告)编译了上面的代码,并在一些机器上运行它;他们中的大多数都在运行 Ubuntu 16.04,一些是较旧的 Linux,一些是较新的 Linux。在这种情况下,我认为操作系统根本不应该相关。结果……好坏参半!