インライン関数(inline)
C99で、関数をインライン展開するinline
指定子が追加されました。インライン展開とは、簡単にいうとある関数を呼び出さずに、その位置に文字列として置き換えてしまうことです。そうすることで、関数呼び出しのためのオーバーヘッドがなくなります。これは、従来関数マクロで行われていました。
便利そうですが、少しややこしい面もあります。まず、コンパイル時に最適化オプション-O1
〜-O3
を付ける必要があります。また、実際にインライン展開されるかは、コンパイラの判断に委ねられます。たとえば、再帰関数はインライン展開されませんし、問題なさそうでも拒否される場合もあります。
『Cクイックリファレンス』116頁
引数で2つの数を受け取り、その2つ数の間の奇数だけを表示するプログラムを書きました。過剰ですが、4つの関数を作成して、そのすべてにinline
指定子をつけました。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
static inline bool isNumber(const char *p);
static inline void printOdd(int m, int n);
static inline void printLineFeed(void);
static inline void usage(void)
{
fprintf(stderr, "1st num < 2nd num\n");
}
int main(int argc, char *argv[])
{
if (argc != 3) {
usage();
return 1;
}
for (int i = 1; i < argc; i++) {
if (!isNumber(argv[i])) {
printf("Not a number\n");
return 1;
}
}
int m = atoi(argv[1]);
int n = atoi(argv[2]);
if (m > n) {
usage();
return 1;
}
printOdd(m, n);
printLineFeed();
return 0;
}
static inline bool isNumber(const char *p)
{
if (*p == '-') {
++p;
}
while (*p != '\0') {
if (*p < '0' || *p > '9') {
return false;
}
++p;
}
return true;
}
static inline void printOdd(int m, int n)
{
for (int i = m; i <= n; i++) {
if (i % 2 != 0) {
printf("%d ", i);
}
}
}
static inline void printLineFeed(void)
{
putc('\n', stdout);
}
$ ./a.out 543 567
543 545 547 549 551 553 555 557 559 561 563 565 567
では、アセンブラにして確認してみましょう。-S
オプションをつけてコンパイルすると、アセンブラが記述された拡張子が.s
のファイルが作成されます。アセンブラは長いので、重要な行の周辺だけを紹介します。まずは、最適化オプションをつけないバージョンです(全355行)。
$ gcc -S inline.c
less inline.s
call _atoi
movl %eax, -12(%rbp)
movl -8(%rbp), %eax
cmpl -12(%rbp), %eax
jle L8
call _usage
movl $1, %eax
jmp L4
L8:
movl -12(%rbp), %edx
movl -8(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call _printOdd
call _printLineFeed
movl $0, %eax
L4:
leave
LCFI5:
ret
call
が関数呼び出しです。_printOdd
や_printLineFeed
がcall
されているのが分かります。
では、最適化オプション-O3
をつけたバージョンはどうなってるでしょうか(全185行)。ファイルを確認したところ、call
されている自作関数は見つかりませんでした。
最後に、速度を測ってみます。-100000000
から-100000000
までの奇数を計算させ、また標準出力の有無も測定しました。
最適化 | 出力 | 速度 |
---|---|---|
なし | stdout |
0m30.372s |
-O3 |
stdout |
0m30.226s |
なし | なし | 0m8.051s |
-O3 |
なし | 0m8.127s |
結果を見るに、速度の違いは殆どありません。もっと大きな数値計算プログラムだと、目に見えるような差が出てくるのかもしれません。