---
# System prepended metadata

title: $\bmod p$ で掛け算をするときの高速化の余地について
tags: [競プロ]

---

---
tags: 競プロ
---
# $\bmod p$ で掛け算をするときの高速化の余地について

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">これめちゃくちゃ高速化の余地だよな <a href="https://t.co/AOpexViDUj">pic.twitter.com/AOpexViDUj</a></p>&mdash; tatyam (@tatyam_prime) <a href="https://twitter.com/tatyam_prime/status/1495613144755933185?ref_src=twsrc%5Etfw">February 21, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

$\bmod p$ で掛け算をするとき、以下のようなコードが現れます。

```cpp
uint32_t mod_mul(uint32_t a, uint32_t b, uint32_t p){
    return static_cast<uint64_t>(a) * b % p;
}
```

$p$ が 32 bit であるとき、$a \times b \bmod p$ を計算するコードです。$a, b$ が 32 bit なので、$a \times b$ を 64 bit で計算する必要があります。

これをコンパイルした結果のアセンブリを見てみましょう。

[Compiler Explorer](https://godbolt.org/) を使用します :bow:

![](https://i.imgur.com/Ybo3UNQ.png)

`movl` は 32 bit の代入、`xorl` は 32 bit の XOR 、`imulq` は 64 bit の符号付き乗算、`divq` は 64 bit の符号なし除算を表します。
64 bit で $a \times b$ を計算するのでまあこうなりますよね。

命令は https://www.felixcloutier.com/x86/ のようなところを見るといいです。

さて、ここで `divq` 命令を詳しく見てみましょう。

https://www.felixcloutier.com/x86/div

> Unsigned divide RDX:RAX by r/m64, with result stored in RAX ← Quotient, RDX ← Remainder.

`RDX` と `RAX` を繋げた 128 bit を引数 64 bit で割って、商を `RAX` に、余りを `RDX` に入れると書いてあります。
64 bit 除算は 128 bit / 64 bit をしているんですね〜

ということは 32 bit 除算で十分なのでは？
64 bit 除算と 32 bit 除算では速度が格段に違うので、できれば 32 bit 除算を使いたいところです。

## 実装

`divl`

> Unsigned divide EDX:EAX by r/m32, with result stored in EAX ← Quotient, EDX ← Remainder.

に対して `mull`

> Unsigned multiply (EDX:EAX ← EAX ∗ r/m32)

がちょうど `EDX:EAX` を共有しているので、これをまとめて最適化しましょう。
コンパイラは最適化をしてくれないようなので、インラインアセンブリで頑張ります。
(コンパイラが最適化しないのにはちゃんと理由があって、$a = 2^{16}, b = 2^{16}, p = 1$ とか入れると $a \times b / p$ の商が 32 bit に収まらなくなって RE を起こします。$a, b < p$ が保証されている場合は、32 bit 除算に最適化することができます。)

https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html



![](https://i.imgur.com/AR6PSvi.png)

`"a"` は `EAX` を使うことを、`"d"` は `EDX` を使うことを、`"r"` はコンパイラに選んでもらうことを表します。

これで、32 bit 除算を使うことができました。

```cpp
u32 mod_mul(u32 a, u32 b, u32 p){
    asm("mull\t%3" : "=a"(a), "=d"(b) : "a"(a), "r"(b));
    asm("divl\t%4" : "=a"(a), "=d"(b) : "a"(a), "d"(b), "r"(p));
    return b;
}
```

## 実測

https://wandbox.org/permlink/kPy0KLjU6EZvHNPQ

1.2 倍くらい速い　思ったより速くならなかった　たぶん `divq` が賢いことをやってる

64 bit もやってみる

$p$ が小さい : https://wandbox.org/permlink/EmkZpkRb3wzXlyCm
$p$ が大きい : https://wandbox.org/permlink/9kvKEATjx60Ew1MW

1.1 – 1.2 倍くらい速い　$p$ によって速度が劇的に変わる　これは何か賢いことをやっていますね…

## おわり

この高速化は https://ei1333.github.io/luzhiled/snippets/string/rolling-hash.html で初めて見ました

基本ジャッジは x86-64 だから動くんだけど、ARM のジャッジってあるのかな