-march=native 諸々

概要

-march=nativeについて色々調べた。

話題の発端

こちらのツイートであると思われる。

シンプルなコードで、しかも-march=nativeを付けた場合のみ壊れる、ということで非常に力がある。自分もこれを機にmarchについて調べてしまった。

そもそもなぜ上記のコードは壊れているのか?

元のコードからC++要素を取り除くと次のようになる。もちろんこのコードも壊れていることがコードテストから確認できる。

#include <cstdio>
#include <cstring>
#include <cassert>

int main() {
    long long a[4] = {1, 1, 1, 0}, b[4];
    memmove(b, a, 4 * sizeof(long long));
    
    for (int i = 0; i < 4; i++) {
      printf("%lld ", b[i]);
    }
    printf("\n");
    return 0;
}

実際にアセンブリを確認すると (godbolt)、vpbroadcastq および vmovdqab{1, 1, 1, 1}で上書きした後 b[3] に対して何もしていないことがわかる。

なお、-march=nativeが実際どう変換されているかはgcc -### -march=native /usr/include/stdlib.hで確認できる。AtCoderだと-march=icelake-server

これと -march=native を付けた場合のみ壊れるという現象から、GCCのAVX512周りになにかバグがあるのだろうと検討が付く。実際にGCCのissue trackerを眺めると、どうやら今回のバグはこれっぽい 108599 – [12 Regression] Incorrect code generation newer intel architectures 。12.3で修正されているので、AtCoderのGCCがアップデートされればこの問題は解決されそう(いつだろう)。

-march=native -mtune=nativeってそもそも何?

とても雑に言うと

  • -march=native: コンパイルしたパソコン(のCPU)専用のa.outを作ってくれという命令。生成されたa.outを他のパソコンにコピーすると、動くかもしれないし動かないかもしれない。
  • -mtune=native: コンパイルしたパソコン(のCPU)向けのa.outを作ってくれという命令。生成されたa.outを他のパソコンにコピーすると、動くけどちょっと遅いかもしれない。

という認識。例えば上記のvpbroadcastq命令が動くパソコンは限られるため、-march=nativeを付けないと使用されない。

なお、x86 Options (Using the GNU Compiler Collection (GCC)) にあるように、-march=native-mtune=nativeを含むため、両方指定する必要はない。

Specifying -march=cpu-type implies -mtune=cpu-type, except where noted otherwise.

-march=nativeって効果あるの?

GCC12からは次の理由で格段に効果が上がっている(GCC11までは-O3と組み合わせないと効果が薄い)。

  • -march=nativeで解禁される命令の大半は自動ベクトル化 (自動ベクトル化について: gcc での自動ベクトル化 Wiki - yukicoder )向けの命令である。
  • GCC11までは-O3を指定しないと自動ベクトル化がonにならなかったが、GCC12からは-O2でonになる。ただし-O3より自動ベクトル化のしきい値が高い(fvect-cost-model=cheap)。

おそらく最も効果があるものの一つはbitsetのand/or/xor/count/any/all等なので、 ABC 329 F で不正を試みる。自明な $O(NQ / w)$ 解をMLE対策に少し工夫して投げると、次のように

約3倍の高速化が確認できる。ただし、C++17でもpragmaをモリモリと付けるとACする

さらに GCC optimize("Ofast")を付けると、C++20より速くなる。

なんか実行時間がめちゃくちゃブレる(インスタンスガチャ?)ので、ブレの範疇な気もする まったく同じコードを2回投げて 1359ms vs 1907ms とか出た ( https://atcoder.jp/contests/abc329/submissions/48871947, https://atcoder.jp/contests/abc329/submissions/48871969 )

(不正以外で)まともに効果がありそうなのは DP 系だろうか、modintをMontgomery乗算で実装するとSIMD(自動ベクトル化)と相性がいいという小ネタもあり、云々

-march=nativeでpragmaって代替きかないの?

大体聞きそうな気はする、懸念点は

  • こだわるとジャッジごとに異なるpragmaを用意する必要がありそうで、ダルい
  • -march=...とpragmaが本当に等価なのかわかってない(例えばyukicoderの記事には #pragma GCC optimize ("O3") は -O3 でのコンパイルとは異なるようです とある)
  • GCC13.2だとなんかpragma使えないかも? https://twitter.com/yosupot/status/1730356100363645093

あたりだろうか

結局 -march=native って危険なの?

もちろんまともな根拠はなくただの直観になるが、「わずかに危険だけど、問題になることはほぼない」程度ではないかと思っている

A recommended default choice for CFLAGS or CXXFLAGS is to use -march=native

  • また、march=nativeのように"コンパイルしたパソコン専用に最適化"という概念自体もかなり使われているはず。確かrustだとcargo installでデフォルトで-march=native相当のオプションがonになったはず

  • そもそもGCCのバグに出会うのがレアイベント

    • 自分は-march=nativeの有無でどうこうというのは初めて出会った気がする、記憶力がないだけか?
    • AtCoderにGCC12と-march=nativeが導入される以前はノーカンという話もある。
  • 一方で、pragmaで大体何とかなりそうだしわざわざ入れる必要がないのではという意見もありそう

皆さんの意見はどうでしょうか(ブン投げ)