Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 高效汇编乘法_C_Assembly_X86_Nasm_Micro Optimization - Fatal编程技术网

C 高效汇编乘法

C 高效汇编乘法,c,assembly,x86,nasm,micro-optimization,C,Assembly,X86,Nasm,Micro Optimization,不久前开始练习组装。 我想通过汇编命令lea和shift实现有效的乘法。 我想写一个c程序,它将调用一个装配过程,该装配过程符合用户接收到的常数参数,并将用户接收到的另一个参数乘以该常数 如何使此代码有效? 我可以将哪些数字分组(如果有)以符合相同的程序? 例如,我认为我可以分组2,4,8,。。。与左移1,2,3的步骤相同 但是我很难找到像这样有其他数字的其他组,那么负数呢…本练习有趣的部分是找到使用1或2 LEA、SHL和/或ADD/SUB指令实现各种常量乘法的方法 实际上,为一次乘法而动态调

不久前开始练习组装。 我想通过汇编命令lea和shift实现有效的乘法。 我想写一个c程序,它将调用一个装配过程,该装配过程符合用户接收到的常数参数,并将用户接收到的另一个参数乘以该常数

如何使此代码有效?
我可以将哪些数字分组(如果有)以符合相同的程序? 例如,我认为我可以分组2,4,8,。。。与左移1,2,3的步骤相同


但是我很难找到像这样有其他数字的其他组,那么负数呢…

本练习有趣的部分是找到使用1或2 LEA、SHL和/或ADD/SUB指令实现各种常量乘法的方法

实际上,为一次乘法而动态调度并不十分有趣,这意味着要么是实际的JIT编译,要么是在一个巨大的代码块表中已经存在了所有可能的序列。(如
开关
语句。)

相反,我建议编写一个C或Python或任何接受1个整数arg的函数,并作为输出生成asm源文本,该文本实现
x*n
,其中
n
是整数arg。
即,类似于您在编译器中找到的优化乘常数的函数

您可能需要制定一种自动化的方法来测试这一点,例如,通过比较两个不同的
x
值与纯C
x*n
进行比较


如果你不能在2条指令中完成任务(或者3条指令中有一条指令是
mov
),那就不值得了。现代x86的硬件乘法效率高得离谱
imul reg、r/m、imm
为1 uop、3周期延迟、完全管道化。(AMD从Zen开始,Intel从Core2或Nehalem开始等等。)对于关键路径长度为1或2个周期无法完成的任何事情,这都是您的退路(如果您愿意,假设零延迟mov,如IvyBridge+和Zen。)

或者,如果您想探索更复杂的序列,可以在回退之前设置更高的阈值,例如,在推土机系列上实现64位乘法(6个周期延迟)。甚至是P5奔腾,其中
imul
需要9个周期(不可配对)


要寻找的模式 整数乘法归结为将1个操作数的移位副本相加,其中另一个操作数具有
1
位。(请参阅执行乘法运行时变量值、移位和加法检查每一位的算法。)

当然,最简单的模式只有一个设置位,即2的幂;那就左移吧。这很容易检查:
n&(n-1)==0
,当
n!=0

任何正好有2个设置位的操作最多只能进行2次移位和一次加法。(GNU C
\uuu内置\uu popcount(n)
对设置位进行计数。在x86 asm中,SSE4.2
popcnt

GNU C
内置ctz
查找最低设置位的位索引。在您知道的非零数字上使用它将获得低位的移位计数。
在x86 asm中,
bsf
/
tzcnt

要清除最低设置位并“暴露”下一个最低设置位,可以执行
n&=n-1。在x86 asm或LEA/AND中


另一个有趣的模式是2n+-1。+1情况已经被2位设置覆盖,但低位的移位计数为0;不需要换班。轮班次数最多为3次时,您可以在一个LEA内完成

通过检查
n+1
是否为2的幂(仅设置了1位),可以检测2^n-1。更复杂的是,
(2^n-1)*2^m
可以通过这个技巧加上另一个移位来完成。因此,您可以尝试右移,将最低设置位移到底部,然后寻找技巧

GCC以2^n-1的方式执行此操作:

mul15:              # gcc -O3 -mtune=bdver2
        mov     eax, edi
        sal     eax, 4
        sub     eax, edi
        ret
clang效率更高(对于缩放索引仍然只有1个周期延迟的Intel CPU):


结合这些模式 也许可以将你的数字分解成它的主要因子,并寻找方法使用你的构建块来组合这些因子

但这不是唯一的方法。您可以按
x*11
的方式执行
x*5*2+x
,就像GCC和Clang这样做(非常类似)

对于x*17也有两种方法。GCC和Clang这样做:

mul17:
        mov     eax, edi
        sal     eax, 4
        add     eax, edi
        ret
但是,即使使用
-march=sandybridge
(无mov消除,1周期
LEA[reg+reg*scale]
)他们也无法使用的另一种方法是:

因此,我们不是将因子相乘,而是将不同的乘数相加,形成总乘数


除了简单的2位或2^n+-1之外,我没有任何关于如何以编程方式搜索这些序列的好建议。如果您感到好奇,可以在GCC或LLVM源代码中查找执行这些优化的函数;他们发现了很多棘手的问题

这项工作可能分为目标中性优化过程和x86特定目标代码的幂2优化过程,用于使用LEA,以及在返回到
imul
-immediate之前决定多少指令值得的阈值


负数
x*-8
可以使用
x-x*9
完成。我认为即使
x*9
溢出,这可能是安全的,但您必须仔细检查


查看编译器输出 我将其用于x86-64系统V ABI(RDI中的第一个参数,如上面的示例)。用gcc和铿锵-O3。我使用了
-mtune=bdver2
(Piledriver),因为它的乘法速度比Intel或Zen稍慢。这鼓励GCC和Clang稍微更积极地避免
imul

我没有尝试如果
long
/
uint64\u t
会改变这一点(6个周期而不是4个周期的延迟,吞吐量的一半),或者如果旧的uarch lik
        lea     eax, [rdi + 4*rdi]
        lea     eax, [rdi + 2*rax]
mul17:
        mov     eax, edi
        sal     eax, 4
        add     eax, edi
        ret
mul17:
        lea    eax,  [rdi + 8*rdi]  ; x*9
        lea    eax,  [rax + 8*rdi]  ; x*9 + x*8 = x*17
#define MULFUN(c) int mul##c(int x) { return x*c; }
MULFUN(9)
MULFUN(10)
MULFUN(11)
MULFUN(12)
...