概要
-march=nativeについて色々調べた。
話題の発端
こちらのツイートであると思われる。
分からん
— AllDirections (@AllDirections4) 2023年12月21日
これ何かの未定義動作踏んでる?? pic.twitter.com/ACV8fb8PjQ
シンプルなコードで、しかも-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
および vmovdqa
で b
を {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対策に少し工夫して投げると、次のように
- (C++20, -march=native): 1352ms / 4s AC https://atcoder.jp/contests/abc329/submissions/48871083
- (C++17, -march=nativeなし): TLE(時々AC) https://atcoder.jp/contests/abc329/submissions/48871094
約3倍の高速化が確認できる。ただし、C++17でもpragmaをモリモリと付けるとACする
- (C++17, pragmaモリモリ): 1767ms / 4s AC https://atcoder.jp/contests/abc329/submissions/48871873
さらに GCC optimize("Ofast")
を付けると、C++20より速くなる。
- (C++17, pragma, Ofast) 971ms https://atcoder.jp/contests/abc329/submissions/48871954
なんか実行時間がめちゃくちゃブレる(インスタンスガチャ?)ので、ブレの範疇な気もする まったく同じコードを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 って危険なの?
もちろんまともな根拠はなくただの直観になるが、「わずかに危険だけど、問題になることはほぼない」程度ではないかと思っている
- Safe CFLAGS - Gentoo wiki Gentoo wiki曰く、
-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で大体何とかなりそうだしわざわざ入れる必要がないのではという意見もありそう
皆さんの意見はどうでしょうか(ブン投げ)