Assembly 使用单个aarch64指令获取余数?

Assembly 使用单个aarch64指令获取余数?,assembly,arm,modulo,arm64,integer-division,Assembly,Arm,Modulo,Arm64,Integer Division,我正在为ARM8(aarch64)编写一些汇编代码。我想进行除法运算,并将得到的余数用于进一步的计算。在x86中,当我使用 “div”,我知道我的余数保存在RDX中。我的问题是-是否有与aarch64指令集中的指令相同的指令?我知道“udiv”和“sdiv”做无符号和有符号除法,并得到商。有没有一条指令可以给我剩余的?(我想要c中的%模运算符)。我知道我可以用代数得到它,只是想确认我没有错过一个更简单的方法。除非两个除数的恒幂可以优化为和,否则没有指令可以计算除法的剩余部分。但是,您可以用两种方

我正在为ARM8(aarch64)编写一些汇编代码。我想进行除法运算,并将得到的余数用于进一步的计算。在x86中,当我使用
“div”,我知道我的余数保存在RDX中。我的问题是-是否有与aarch64指令集中的指令相同的指令?我知道“udiv”和“sdiv”做无符号和有符号除法,并得到商。有没有一条指令可以给我剩余的?(我想要c中的%模运算符)。我知道我可以用代数得到它,只是想确认我没有错过一个更简单的方法。

除非两个除数的恒幂可以优化为
,否则没有指令可以计算除法的剩余部分。但是,您可以用两种方法巧妙地完成:

// input: x0=dividend, x1=divisor
udiv x2, x0, x1
msub x3, x2, x1, x0
// result: x2=quotient, x3=remainder
计算余数不是一条指令 Clang C编译器为模计算生成了以下代码:

udiv    x10, x0, x9
msub    x10, x10, x9, x0
好消息,这并不慢! 虽然x86在一条指令中完成了这项工作,但这并不能使它更快

在苹果M-1上,上述指令对的执行时间与单个步骤大致相同。这可能是由于将多条指令解码为单个µ-op。也可能是由于多条指令的并行性。它可能是在一个EU中完成的,其中除法计算的余数被缓存并立即返回

无论是什么实现,它似乎都与Intel的单指令形式一样快

仅限分部 时间:

$ time ./a.out 12345678901
Total: 301123495054
real    0m10.036s
user    0m9.668s
sys 0m0.031s
$ time ./a.out 12345678901
Total: 8612082846779832640
real    0m10.190s
user    0m9.768s
sys 0m0.070s
$ time ./a.out 12345678901
Total: 8612083123211969892
real    0m10.103s
user    0m9.752s
sys 0m0.019s
生成的指令:

udiv    x10, x0, x9
udiv    x10, x0, x9
msub    x10, x10, x9, x0
udiv    x10, x0, x9
msub    x11, x10, x9, x0
仅余数 时间:

$ time ./a.out 12345678901
Total: 301123495054
real    0m10.036s
user    0m9.668s
sys 0m0.031s
$ time ./a.out 12345678901
Total: 8612082846779832640
real    0m10.190s
user    0m9.768s
sys 0m0.070s
$ time ./a.out 12345678901
Total: 8612083123211969892
real    0m10.103s
user    0m9.752s
sys 0m0.019s
生成的指令:

udiv    x10, x0, x9
udiv    x10, x0, x9
msub    x10, x10, x9, x0
udiv    x10, x0, x9
msub    x11, x10, x9, x0
除法与余数 时间:

$ time ./a.out 12345678901
Total: 301123495054
real    0m10.036s
user    0m9.668s
sys 0m0.031s
$ time ./a.out 12345678901
Total: 8612082846779832640
real    0m10.190s
user    0m9.768s
sys 0m0.070s
$ time ./a.out 12345678901
Total: 8612083123211969892
real    0m10.103s
user    0m9.752s
sys 0m0.019s
生成的指令:

udiv    x10, x0, x9
udiv    x10, x0, x9
msub    x10, x10, x9, x0
udiv    x10, x0, x9
msub    x11, x10, x9, x0
基准代码 以下C代码可以在注释掉
q=n/d
r=n%d
的情况下运行:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    unsigned long long n, d, q=1, r=1, total=0;

    n = strtoull(argv[1], NULL, 10);
    total = 0;
    for (d=1 ; d<=n ; d++) {
        q = n / d;
        r = n % d;
        total += q + r;
    }
    printf("Total: %llu", total);
    return 0;
}
#包括
#包括
int main(int argc,char*argv[])
{
无符号长n,d,q=1,r=1,总计=0;
n=strtoull(argv[1],NULL,10);
总数=0;

对于(d=1;dYou尚未证明这是一个“单步”(融合到1个宽指令中,如mov/movk);我想你已经证明了
udiv
的吞吐量瓶颈可以很好地隐藏
msub
的额外工作,这是意料之中的。我猜通过
%
的依赖链比通过
/
的dep链具有更高的延迟,也就是说,如果下一次迭代的输入依赖于
%
迭代,如
x%=d
,如果可以将其安排为不进行优化,例如,通过减少
d
而不是增加,这样就不能证明它是无效的。@PeterCordes如果涉及多个执行单元,则计时不构成证明。也就是说,很可能给出1)宏融合是一种众所周知且经过验证的技术,2)基本上所有合理的整数除法硬件实现都会同时产生一个余数,3)不合并这些步骤是浪费,4)丹尼尔·莱米尔也表明M-1融合mul和umulh的原因相同。有趣的是,这是完全可能的,而且如果使用多个fusion,则可能性相当大。不这样做的主要原因是避免一个“uop”产生两个寄存器输出;AArch64通常会避免这种情况,除了
ldp
(或指令或任何M-1称为内部管道槽的东西)这就是AArch64将
umull
分为
umull
umulh
,或
vuzp
分为
uzp1
/
uzp2
的原因。但是,如果解码器能够找到他们想要查找的所有对,高性能实现可以融合。另一个测试是尝试一个不相关的msub,我们知道它不能融合e、 根据Dougall J,这些是:其他测试的模式没有融合,包括adrp+add、mov+movk、mul+umulh和udiv+msub