インライン関数とマクロのビルド時間に与えるインパクト

  • 8
    いいね
  • 2
    コメント

はじめに

C言語において特定処理の関数呼び出しオーバーヘッドを無くしたい場合にはインライン関数とマクロが使えます。本記事では、両者を使った場合のプログラムの実行時間の差ではなく、ビルド時間の差に注目して、どちらの場合がどれだけ速いかを確認します。コンパイラもgccとclangの2つを使用しました。

単純な例

2つのint型の変数の内容を入れ替えるswap()という処理を、インライン関数とマクロで二通りの実装をします。

static void swap(int *a, int *b) _attribute((always_inline));

static inline void swap(int *a, int *b)
{
        int tmp = *a;
        *a = *b;
        *b = tmp;
}
...
#define swap(a, b) do { typeof(a) __tmp = a; a = b; b = __tmp; } while (0)

これらのビルド時の呼び出しコストは一体どれくらいなのを比較します。具体的には、それぞれの処理を所定の回数呼び出すコードを作成して、そのビルドに要した所要時間(timeコマンドのreal行の値)を比較します。

ソースを書く際に注意しなければいけないのは、今注目しているのはビルドしたプログラムの所要時間ではなく、ビルド処理自身の所要時間であるということです。具体的には、swap()を10回呼び出す際のコストを知るためには、以下のようにループ内でswap()を呼び出してはいけません。

...
int main(void)
{
        int a = 0, b = 1, i;
        for (i = 0; i < 10; i++) {
                swap(a, b);
        }
        return 0;
}

このようなソースだと、ビルドの結果できるプログラムでは確かにswap()は10回呼ばれるのですが、今注目しているビルド(プリプロセス&コンパイル)の際にはswap()の呼び出し部分は一回しか解釈されません。したがって、次のようにswap()を明に10回呼び出す必要があります。

...
int main(void)
{
        int a = 0, b = 1;
        swap(a, b);
        swap(a, b);
        swap(a, b);
        swap(a, b);
        swap(a, b);
        swap(a, b);
        swap(a, b);
        swap(a, b);
        swap(a, b);
        swap(a, b);
        return 0;
}

測定環境

  • OS: Debian GNU/Linux 9.0
  • gcc: version 6.3.0 20170205 (Debian 6.3.0-6)
  • clang: version 3.8.1-17 (tags/RELEASE_381/final)

測定方法

インライン関数版のswap()とマクロ版のそれを、それぞれ100回刻みで0, 100, 200, ... 10,000 回呼び出した際のビルド時間を測定する。

測定結果

gcc

image.png

見てわかるように、インライン版に比べてマクロ版のほうがはるかに時間がかかっていることがわかります。しかもマクロ版の所要時間は非線形に増加しているように見えます。筆者はgccの内部構造には明るくないので、なぜこのようなことになるかはわかりません。いずれにせよ、この単純なテストにおいては明らかにインライン版のほうが性能がよいことは確かです。

clang

image (1).png

gccの場合と同様マクロ版のほうがインライン関数版よりも所要時間が多いですが、その差はgccの場合に比べて微々たるものでした。グラフの形も線形です。gccに比べてやや性能にばらつきがあるのも特徴です(何度か計測しましたが傾向は同じでした)。

比較

image (2).png

呼び出し回数が少ない場合を除いて、全体として速度はインライン関数(gcc)>インライン関数(clang)>マクロ(clang)>>マクロ(gcc)という順番でした。このうち最初の3つは大した違いが無く、マクロ版のgccだけが際立って遅かったです。少なくともswap()程度の規模については、gccのプリプロセッサの性能がclangのそれに比べて極めて遅いということは言えそうです。…と思っていましたが、間違っているというコメントをいただきました。実際にはプリプロセッサが処理した後の最適化段階で時間がかかっていたようです。

インライン化/マクロ化する処理の引数の数、処理の規模、呼び出す場所(今回は同じ関数からだけ呼び出したが、実際はその限りではない)等によって具体的な数値は変わってくるはずなので、興味のあるかたは色々な処理をインライン化/マクロ化してデータの変化を見てみるのも面白いかもしれません。

おわりに

本記事で使ったソースはgithub上に置いています。今回使ったデータはソース内のsampleディレクトリ以下においています。興味があればご参照ください。