【问题标题】:Obtaining remainder using single aarch64 instruction?使用单个 aarch64 指令获取余数?
【发布时间】:2016-05-22 22:41:45
【问题描述】:

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

【问题讨论】:

    标签: assembly arm modulo arm64 integer-division


    【解决方案1】:

    除了可以优化到and 的恒定二次除数之外,没有指令可以计算除法的余数。但是,您可以非常巧妙地分两步完成:

    // input: x0=dividend, x1=divisor
    udiv x2, x0, x1
    msub x3, x2, x1, x0
    // result: x2=quotient, x3=remainder
    

    【讨论】:

      【解决方案2】:

      计算余数不是一条指令

      Clang C 编译器为模计算生成了以下代码:

      udiv    x10, x0, x9
      msub    x10, x10, x9, x0
      

      好消息,这并不慢!

      虽然 x86 在一条指令中执行此操作,但这并不能使其更快。

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

      无论实现什么,它似乎都和英特尔的单指令形式一样快。

      仅限分区

      时间:

      $ time ./a.out 12345678901
      Total: 301123495054
      real    0m10.036s
      user    0m9.668s
      sys 0m0.031s
      

      指令生成:

      udiv    x10, x0, x9
      

      仅剩余

      时间:

      $ time ./a.out 12345678901
      Total: 8612082846779832640
      real    0m10.190s
      user    0m9.768s
      sys 0m0.070s
      

      生成的指令:

      udiv    x10, x0, x9
      msub    x10, x10, x9, x0
      

      除法和余数

      时间:

      $ time ./a.out 12345678901
      Total: 8612083123211969892
      real    0m10.103s
      user    0m9.752s
      sys 0m0.019s
      

      生成的指令:

      udiv    x10, x0, x9
      msub    x11, x10, x9, x0
      

      基准代码

      以下 C 代码可以在 q = n / dr = 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;
      }
      

      【讨论】:

      • 您还没有证明它是“单步”(融合到 mov/movk 等 1 条宽指令中);我认为您已经证明udiv 的吞吐量瓶颈可以很好地隐藏msub 的额外工作,这是意料之中的。我猜想通过% 的依赖链将比通过/ 的dep 链具有更高的延迟,即如果下一次迭代的输入依赖于% 这个迭代,比如x %= d,如果可以安排的话不优化,例如通过递减 d 而不是递增,因此它无法证明它是空操作。
      • @PeterCordes 鉴于涉及多个执行单元,时间不构成证据。也就是说,很可能是因为 1) 宏融合是一种众所周知且经过验证的技术,2) 基本上所有合理的整数除法硬件实现都会同时产生余数,3) 不组合这些步骤会很浪费,以及4) Daniel Lemire 还证明了 M-1 融合 mulumulh 的原因相同。
      • 有趣;是的,这完全有可能,如果 mul fusion 很有可能。 这样做的主要原因是避免让一个“uop”产生 2 个寄存器输出;除了ldp 之外,AArch64 通常会避免一些东西。 (或指令或任何 M-1 调用的内部流水线槽)。这就是为什么 AArch64 将 umull 拆分为 umullumulh,或将 vuzp 拆分为 uzp1 / uzp2。但是,是的,如果解码器可以找到他们想要查找的所有对,那么高性能实现可以融合。
      • 另一个测试是尝试一个不相关的 msub,我们知道它不能融合,看看它是否更慢。
      • Per Dougall J,这些是not fused未融合其他测试模式,包括 adrp + add、mov + movk、mul + umulh 和 udiv + msub。(强调我的)
      猜你喜欢
      • 2022-07-16
      • 1970-01-01
      • 1970-01-01
      • 2018-06-13
      • 2016-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多