微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

ADD vs OR vs XOR 计算索引

如何解决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+ba|ba^b 产生相同的值,只要不是两个位都设置在相同的位位置。 之所以有效,是因为索引只是 x 和 y 位的串联:


                    lowest  bits
                     v        v
index = 0b...yyyyyyyyyxxxxxxxxx
                      \___ ___/
                          '
                  stride_shift bits

所以问题是,在 x86(-64) 中什么可能更快?可读代码推荐什么?

  • 使用 + 会不会有加速,因为编译器可能同时使用 ADDLEA 指令,这样可以在两个指令上同时执行多个加法地址计算单元和 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 举报,一经查实,本站将立刻删除。