Intelコンパイラでインライン展開上限の設定と挙動


## はじめに

C++での高速化ではインライン展開が重要ですが、一方で現実的には全ての関数呼び出しがinline展開されるわけではありません。

そのインライン展開具合の調整法の一つとして、コンパイラオプションで設定値を変えることができますが、どうやら単純ではない模様です。今回はその情報共有目的の記事です。

※今回はIntelコンパイラ(icc,icpc)を対象とした話題です。gccやclangは扱いません。

## はじめに

Intelコンパイラにおいてインライン展開具合を設定できるものとして、次のコンパイラオプションがあります(他にもあります)。

- `-inline-max-size=n`：呼び出される側の関数のサイズが設定値`n`以下であればインライン展開される（されやすい）
- `-inline-max-total-size=n`：インライン展開した場合に呼び出す側の関数のサイズが設定値`n`を超えるならインライン展開しない（されにくい）
- `inline-factor=N`：倍率`N`[パーセント(ただし整数)]をdefault値に掛ける形で上記二つの設定を同時に行える。

これらの設定値のdefault値は、最適化レポートファイル`*.optrpt`の冒頭に記載されます。私が検証した環境では、`-inline-max-size`、`-inline-max-total-size`、`-inline-factor`のdefault値はそれぞれ230、2000、100でした。

## 例

実際にインライン展開されたかどうかは、最適化レポートファイル`*.optrpt`を見ることで分かります。ただし、コンパイラオプションに`-qopt-report=5 -qopt-report-phase=ipo`などを指定してください。

今回は、例として次のオプションでコンパイルした場合を示します。


```
icpc -O2 -ip -inline-max-size=230 -qopt-report=5 -qopt-report-phase=ipo hoge.cpp
```

この場合に`hoge.optrpt`に出力された最適化レポートは次のようなものでした(見やすさのために適当に編集していますが、実際のコードのコンパイル結果から抽出しています)


```
...(略)...
INLINING OPTION VALUES:
  -inline-factor: 100
  -inline-min-size: 30
  -inline-max-size: 230 (user-specified)
  -inline-max-total-size: 2000
  -inline-max-per-routine: disabled
  -inline-max-per-compile: disabled
...
(略)
...
INLINE REPORT: (funcA(int) const) [2147/3416=62.9%] ..hoge.cpp(55,135)
  -> INLINE (MANUAL): (56,18) funcB(int, double) const (isz = 49) (sz = 66)
    -> fuga.h:(809,9) funcC(int) const (isz = 452) (sz = 473)
       [[ Inlining would exceed -inline-max-size value (473>230) <1>]]
    -> INLINE (MANUAL): fuga.h:(809,43) funcD(int) const (isz = 16) (sz = 25)
      -> INLINE (MANUAL): fuga.h:(220,50) funcE(double) (isz = 4) (sz = 15)

```

構造としては`funcA`が`funcB`と呼び出し、`funcB`から`funcC`と`funcD`を呼び出し、さらに`funcD`から`funcE`を呼び出しています。

ここで`funcC`だけはインライン展開されなかったことが分かります。その原因として「`funcC`のサイズが473であり、`-inline-max-size`の値である230を超えた」ためと記されています。

ここで`funcC`のサイズとは、`funcC`の中で呼ばれた関数がインライン展開されている場合には、それらを含んだサイズになります。実際に、`funcC`の中では数十個の細かな関数がcallされて、それらが全てインライン展開されたために、合計サイズが473まで大きくなっています。

また、コンパイルオプションで明示的に`-inline-max-size=230`を指定したので、冒頭の設定値の通知部分で`(user-specified)`と表示されています。

## 問題

先の例では敢えて明示的に`-inline-max-size=230`を指定していましたが、これを指定しなかった場合はどうなるでしょうか。

少なくとも私は、`-inline-max-size`のdefault値は230なので結果は変わらないと思っていました。しかし、やってみると`funcC`がインライン展開される場合があるのです。

例えばコンパイルオプション


```
icpc -O2 -ip -qopt-report=5 -qopt-report-phase=ipo hoge.cpp
```

から、次の最適化レポートが出力されました。


```
...(略)...
INLINING OPTION VALUES:
  -inline-factor: 100
  -inline-min-size: 30
  -inline-max-size: 230
  -inline-max-total-size: 2000
  -inline-max-per-routine: disabled
  -inline-max-per-compile: disabled
...
(略)
...
INLINE REPORT: (funcA(int) const) [2147/3416=62.9%] ..hoge.cpp(55,135)
  -> INLINE (MANUAL): (56,18) funcB(int, double) const (isz = 49) (sz = 66)
    -> INLINE (MANUAL): fuga.h:(809,9) funcC(int) const (isz = 452) (sz = 473)
      -> INLINE (MANUAL): fuga.h:(530,48) funcF(double) (isz = 24) (sz = 32)
      ... (略) funcCから呼び出した数十個の関数...
    -> INLINE (MANUAL): fuga.h:(809,43) funcD(int) const (isz = 16) (sz = 25)
      -> INLINE (MANUAL): fuga.h:(220,50) funcE(double) (isz = 4) (sz = 15)

```

今度は`funcC`もインライン展開されています。

しかし`funcC`のサイズは先と変わらず473です。`-inline-max-size`の設定値も230と表示されています。しかしそれらが加味されずにインライン展開されてしまっています。

同様の問題は`-inline-max-size`以外にも`-inline-max-total-size`や`-inline-factor`を明示的に指定するかどうかでも起こります。

## 想像される原因

さて、この違いは何が原因なのでしょうか。

Intelコンパイラのマニュアル[^1]によれば、「`-inline-max-size`等はコンパイラがルーチンを大・中・小に分類する指標であり、中・小に分類されたものはインライン展開**されやすい**」と読めます。つまり、インライン展開をするかどうかを絶対的に指定できるわけではなさそうです

そして、たとえ同じ設定値であったとしても、「**コンパイラオプションに明示的に指定した場合はインライン展開の判断への影響力が大きくなる**」のだと想像できます。

[^1]: [Intel C++ Compiler Classic Developer Guide and Reference](https://software.intel.com/content/www/us/en/develop/documentation/cpp-compiler-developer-guide-and-reference/top/compiler-reference/compiler-options/compiler-option-details/inlining-options/inline-max-size-qinline-max-size.html)

## おわりに

ということで、`-inline-max-size`や`-inline-factor`での指定では注意が必要です。例えば前回書いた記事[^2]でも`-inline-factor`で設定値を変更して計算速度のベンチを行っていますが、`-inline-factor=100`を明記した場合と未指定の場合で結果が違う可能性が出てきました（うわ～再調査か～）。

個人的には結構ハマり、チューニングにおいてかなり混乱していました。

誰かのお役に立てば幸いです。

[^2]: [Devtoolset環境で-O2より-O3のが遅い](https://qiita.com/pochman/items/59aee10f69d050e5146b)

## 追記１

ここでの議論を踏まえて、高速化のためにはどうすればよいのか。
そもそも重要な観点は

1. サイズは小さいが沢山呼ばれる関数はインライン展開がされた方が良い
2. インライン展開し過ぎてバイナリサイズが大きくなると速度は低下する

の2点です。

前者を促進するためには

(1-1)明示的に`-inline-max-size`をdefaultより大きい値で指定するか、(1-2)`-inline-max-size`は指定せずにコンパイラが(勝手にdefault値の制限以上に)アグレッシブにインライン展開するのを期待するのが良さそうです。

一方で後者を踏まえてバイナリサイズを抑制するには
(2-1)明示的に`-inline-max-total-size`を小さめ(ただしdefaultと同じかちょっと大きい程度)に指定するのが良いでしょう。ただし、(2-NG)`-inline-max-total-size`を指定しないのは問題が出やすいです。こちらの値についてもコンパイラが勝手にdefault値の制限以上にアグレッシブにインライン展開してしまうので、バイナリサイズが大きくなってしまいます。

また、この2点の観点から`-inline-max-size`と`-inline-max-total-size`を共に大きくしていくのは問題があることになります。つまり、`-inline-factor`はあまり使わない方が良さそうです。

## 蛇足

コンパイラオプションで`-inline-factor`などを調整するよりも、Intelコンパイラなら`#pragma forceinline recursive`などを使った方が良いかもしれない。

参考：[Intel developper zone](https://software.intel.com/content/www/us/en/develop/documentation/cpp-compiler-developer-guide-and-reference/top/compiler-reference/pragmas/intel-specific-pragma-reference/inline-noinline-forceinline.html)