作曲・指導・C言語・Linux

金沢音楽制作

金沢音楽制作では、楽曲・楽譜の制作と、作曲や写譜などレッスンを行っています。

インライン関数(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_printLineFeedcallされているのが分かります。

では、最適化オプション-O3をつけたバージョンはどうなってるでしょうか(全185行)。ファイルを確認したところ、callされている自作関数は見つかりませんでした。

最後に、速度を測ってみます。-100000000から-100000000までの奇数を計算させ、また標準出力の有無も測定しました。

最適化 出力 速度
なし stdout 0m30.372s
-O3 stdout 0m30.226s
なし なし 0m8.051s
-O3 なし 0m8.127s

結果を見るに、速度の違いは殆どありません。もっと大きな数値計算プログラムだと、目に見えるような差が出てくるのかもしれません。

更新情報