如何解决ADD vs OR vs XOR 计算索引
处理宽度为 2 的幂的二维数组时,有多种方法可以计算 (x,y) 对的索引:
给定
size_t width,height,stride_shift;
auto array = std::vector<T>(width*height);
size_t x,y;
assert(width == ((size_t)1 << stride_shift));
assert(x < width)
assert(y < height);
第一种方式 - 添加
size_t index = x + (y << stride_shift);
第二种方式 - 按位或
size_t index = x | (y << stride_shift);
第三种方式 - 按位异或
size_t index = x ^ (y << stride_shift);
然后像这样使用
auto& ref = array[index];
...
所有产生相同的结果,因为 a+b
、a|b
和 a^b
产生相同的值,只要不是两个位都设置在相同的位位置。
之所以有效,是因为索引只是 x 和 y 位的串联:
lowest bits
v v
index = 0b...yyyyyyyyyxxxxxxxxx
\___ ___/
'
stride_shift bits
所以问题是,在 x86(-64) 中什么可能更快?可读代码推荐什么?
- 使用
+
会不会有加速,因为编译器可能同时使用ADD
和LEA
指令,这样可以在两个指令上同时执行多个加法地址计算单元和 ALU。 - OR 和 XOR 可能会更快吗?因为每个新位的计算都独立于所有其他位,这意味着没有位依赖于低位的进位。
- 考虑一个嵌套的 for 循环,y 在外部迭代。是否有编译器在循环中进行优化,其中为
row_pointer = array.data()+(y << stride_shift)
的每次迭代计算y
。然后pointer = row_pointer+x
。我认为使用|
或^
无法进行这种优化。 - 混合按位运算和算术运算有时被认为是糟糕的代码,那么应该选择
|
来搭配<<
吗?
作为奖励:这如何适用于一般/其他架构?
解决方法
我希望 3 个版本之间没有区别;在现代 x86 架构上,|
、^
和 LEA
都具有相同的延迟 1、吞吐量 0.5(包括 x + (y << stride_shift)
r+r*s 变体)并且都在ALU。
所以我会专注于代码的可读性。
x + y * width
确实看起来更具可读性(也许更好:+
,它应该被优化为一个转变)。
但可以肯定的是,我们可以非常快速地对其进行基准测试。
来自 quick-bench 的测试结果(Clang 11 结果,GCC 目前正在运行):
MOV
版本中的明显加速似乎是由于 LEA
的输出寄存器减少了 1 个 +
(请参阅 godbolt 链接)。但这可能只是人为测试的产物。在实际代码中,优化器有足够的空间来隐藏这种延迟。
但无论如何,[
[
1618170480000,"59594.60000000","59625.00000000","59557.13000000","59595.05000000","32.64148000",1618170539999,"1945185.17004597",1209,"14.78751100","881221.83660417","0"
],[
1618170540000,"59669.81000000","59564.22000000","59630.16000000","27.45082600",1618170599999,"1636424.61486602",1066,"10.24907000","610941.51532090","0"
]
]
版本似乎都赢了。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。