【问题标题】:How to avoid multiplication in pointer arithmetic?如何避免指针运算中的乘法?
【发布时间】:2014-03-12 06:27:36
【问题描述】:

如果我写

int main(int argc, char *argv[])
{
    int temp[50][3];
    return &temp[argc] - &temp[0];
}

并用 Visual C++ 编译它,我回来了:

009360D0 55                   push        ebp  
009360D1 8B EC                mov         ebp,esp  
009360D3 8B 45 08             mov         eax,dword ptr [argc]  
009360D6 8D 0C 40             lea         ecx,[eax+eax*2]  
009360D9 B8 AB AA AA 2A       mov         eax,2AAAAAABh  
009360DE C1 E1 02             shl         ecx,2  
009360E1 F7 E9                imul        ecx  
009360E3 D1 FA                sar         edx,1  
009360E5 8B C2                mov         eax,edx  
009360E7 C1 E8 1F             shr         eax,1Fh  
009360EA 03 C2                add         eax,edx  
009360EC 5D                   pop         ebp  
009360ED C3                   ret  

为什么我在这里得到一个imul 指令而不是位移等?我觉得这很烦人,因为我在一个紧密的循环中做这样的指针算术,我怀疑imul 正在扼杀它的性能。无论如何,这应该是没有必要的。

有什么好的方法可以防止它,而是用更便宜的操作来代替它?

更新:

在我的原始程序中,我尝试添加一个虚拟变量以使每个元素的大小为 4 而不是 3 的倍数,以便编译器可以使用位移而不是除法。

结果?即使数据结构更大,程序的运行时间也从 9.2 秒减少到 7.4 秒。

所以是的,这确实很慢。

【问题讨论】:

  • 您是否尝试过用位移位替换 mul 指令并对结果进行基准测试?
  • @larsmans:不,因为我使用的是向量迭代器(它们是下面的指针),因此我不一定可以取消引用它们来取回原始指针。如果我这样做了,我可能会不小心取消引用过去的迭代器,从而导致未定义的行为。 (检查过去的迭代器本身会增加额外的指令。)
  • 嗯,优化级别是多少? gcc at -O2
  • 但是,如果输入不能保证是向量迭代器,那么“将是指针”的假设是相当大胆的。
  • “我怀疑 imul 正在扼杀它的性能”......你最好检查一下。你的编译器对这些东西几乎总是正确的。

标签: c++ visual-c++ code-generation multiplication pointer-arithmetic


【解决方案1】:

为什么我在这里得到一个imul 指令而不是位移等?

乘法是除以 3(每个内部数组的大小),使用 0x2AAAAAAB 是 231/3 的事实。你不能通过少量的班次和添加来做到这一点;乘法确实是最快的选择。

我怀疑imul 正在扼杀它的性能。

在大多数现代平台上,整数乘法通常与更简单的运算一样快,因此它可能是最快的选择,即使它可以被几个移位和加法替换。当您遇到性能问题时,请始终测量以找到真正的瓶颈;它们通常位于您最不怀疑的地方。

有什么好的方法可以防止它,而是用更便宜的操作来代替它?

在乘法确实很昂贵的平台上:避免使用大小笨拙的数据结构的数组;并避免减去指针,因为这需要除以对象大小。

【讨论】:

  • 只想提一下,即使在 x86 上,这确实会降低性能(请参阅我的更新)。所以你的最后一段是正确的,谢谢。
  • 整数乘法速度很快(imul eax, ecx 是 3c 延迟 / 1c 吞吐量在 Intel 自 Nehalem 和 AMD 自 Ryzen),但整数 add 更快:1c 延迟 / 0.25c 吞吐量(英特尔自 Haswell 以来)。对于可以自动矢量化的代码,SIMD 整数加法也比 SIMD 整数乘法便宜得多,例如 10c 与 1c 延迟。 (以及与标量整数类似的吞吐量差异)。见agner.org/optimize。但是,是的,imul 比多次移位/添加要好,并且是单个 uop(或者对于产生高半结果的版本来说是几个 uop,就像这里一样)。
  • gcc 和 clang 都使用最多 2 个 LEA 或其他指令来乘以小常数或 2 的幂,但使用 imul 而不是其他 3 个指令。
猜你喜欢
  • 2018-01-24
  • 2021-10-13
  • 2017-02-18
  • 2011-10-01
  • 2011-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-19
相关资源
最近更新 更多