以下の短いコードは、g++ -O2 -g main.cpp -cなどとコンパイルすると長い時間待たされた後、note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying withoutと出力され、その直後にコンパイルが終わります。
#include <array> int f(); struct Elem { Elem() { f(); } }; struct NTD { ~NTD(); }; struct Foo { NTD ntd; std::array<Elem, 5500> arr; Foo(); }; Foo::Foo() : arr{} {}
この現象の発生するコンパイラ
Compiler Explorerで確認したところ、この現象が起こるg++のバージョンは、gcc-4.7.1(-std=c++11が使える最初のg++)からgcc-11.4.0まででした。
この現象の発生条件
以下をすべて満たしたときだと思います。
- 次をすべて満たすクラス(例では
Foo)がある- 次の条件を満たすクラス(例では
Elem)が要素のstd::arrayで、それなりの要素数のもの(例ではarr)をメンバ変数として持つ- デフォルトコンストラクタ(例では
Elem::Elem())の中で例外を送出しうる関数呼び出し(例ではf())を行う- 移譲先やメンバ初期化の右辺であっても該当する
noexcept指定されていれば該当しないnoexcept指定されていなくても、中身を見て例外が出ないと明らかであれば、該当しない
- デフォルトコンストラクタ(例では
- その
std::arrayよりも前に、実質的な中身のあるデストラクタを持つメンバ変数(例ではntd)を持つ - コンストラクタ(例では
Foo::Foo())の中でそのstd::arrayを値初期化({}で初期化)する - そのコンストラクタのコードが生成される
- クラス内で定義したけど実際には使わない、とかだとコードが生成されない
- 次の条件を満たすクラス(例では
- 最適化オプションをつける
-gをつける
出力コードを見たり条件を見たりするとわかりますが、どうやらarrの初期化中に例外が送出されてntdのデストラクタを呼ぶ場合があるかを気にしているようです。
コンパイルにかかる時間
私の環境(Ubuntu 20.04.6 LTS on WSL2, g++-9 (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, Intel(R) Core(TM) i9-12900K)では、配列サイズを変えたときのコンパイル時間は以下のようになりました。
| 配列サイズ | コンパイル時間 |
|---|---|
| 5500 | 1.4秒 |
| 10000 | 2.5秒 |
| 20000 | 6.2秒 |
| 40000 | 18秒 |
| 80000 | 90秒 |
となりました。コンパイル時間が超線形に増大していくことがわかります。
note: variable tracking size limit exceeded with ‘-fvar-tracking-assignments’, retrying withoutというエラーメッセージを見ると、コンパイルの途中でなんかの上限に達したからあきらめる、というような動作を想像します。
しかし、このコンパイル時間を見ると、そうではないようです。
どうやら最後まで処理してから、その結果が上限を超えているのでやりなおす、という動作のようです。
なので、上限を変更したりせずにg++をデフォルトの設定で使っているだけなのに、ものすごくコンパイルが遅くなる現象が発生します。
原因
g++でコンパイルが非常に遅くなる短い例 - よーると似ています。
根本的な原因は、std::arrayを値初期化するときに配列要素のコンストラクタ呼び出しループがアンローリングされ、大量のデバッグ情報が生成されることです。
対処法
g++でコンパイルが非常に遅くなる短い例 - よーるとほぼ同じです。 つまり、
- 最適化をかけないでコンパイルする
-gをつけないでコンパイルするg++ではなくclang++を使ってコンパイルする- コンストラクタでの初期化を値初期化ではなくデフォルト初期化(何も書かない)にする(
std::arrayはそうなっている。ただし、intのような非クラス型のデフォルト初期化は「未初期化」なので注意しなければいけない)
しかし、4年前と違い、今であれば根本的な解決策があります。
それは、g++-12以降を使うことです。
g++-12以降ではアンローリングされずにちゃんとループになるので、このような問題は発生しません。
なお、variable tracking size limit exceededが出ないようにしたいだけであれば、noexceptをちゃんとつける、といった方法があります。
ただし、結局デバッグ情報が大量生成されることには変わりがない(g++でコンパイルが非常に遅くなる短い例 - よーるの問題が残る)ため、コンパイルは遅いままです。