LoginSignup
5
5

More than 5 years have passed since last update.

基本の話: 「変数や関数を節約すればパフォーマンスが上がる」わけではない

Last updated at Posted at 2015-02-04

注意

この記事を書いている人は、日常的に C を書いているわけではありません。
普段は Python とか Java とか書いてます。

もしかすると見当違いの内容があるかもしれません。
補足・ツッコミ・愛のマサカリは、ぜひお願いします…。

経緯

去年、情報系の専門学校に通う学生(以下、その子)から、 C 言語のソースコードをみせてもらって、添削することがあった。

C 言語を使いこなせてるとは、お世辞にもいえない私が見ても、その子のコードはいろいろと厳しい感じだった。

その子とよく話してみると 変数や関数の宣言をしないことが、資源の節約になると思い込んでいる ようだった。

その時は「節約にならない」という内容を口頭で伝えたのだけど、コードにしたほうがわかりやすいので、検証して記事を書いた。

今回の例示に使うコンパイラとバージョン

私の OS X に入ってた LLVM の cc を使った。

$ cc --version
Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.1.0
Thread model: posix

ファイル

24 と出力するだけの C 言語のソースが 3 つある。
この 3 つのファイル a.c, b.c, c.c をコンパイルしよう。

a.c
#include <stdio.h>

int main(void)
{
    printf("%d\n", 24);
    return 0;
}
b.c
#include <stdio.h>

int main(void)
{
    int i = 1;
    int j = 2;
    int k = 3;
    int l = 4;
    int m = i * j * k * l;
    printf("%d\n", m);
    return 0;
}
c.c
#include <stdio.h>

static int x2(int x)
{
    return x * 2;
}

static int x3(int x)
{
    return x * 3;
}

static int x4(int x)
{
    return x2(x2(x));
}

int main(void)
{
    int x = x4(x3(x2(1)));
    printf("%d\n", x);
    return 0;
}
  • a.c は値をほぼそのまま表示してる
  • b.c は変数を何個も使ってる。
  • c.c は関数を定義して何個も呼び出している

コンパイル

これをそれぞれ最適化オプション -O3 でコンパイルする

$ cc -c -O3 a.c b.c c.c

比較

バイナリを比較するため、 cmp コマンドを使う。

コマンドの実行例
$ cmp a.o a.o
$ cmp a.o d.o
a.o d.o differ: char 1153, line 1

ファイルを 2 つ指定し、 2 つの中身が同じなら何も出力せず、
違う場合はその違いを表示するコマンド。

さっき作ったファイルを比較してみると

$ cmp a.o b.o
$ cmp b.o c.o
$ cmp c.o a.o

実は 3 つとも全く同じ実行ファイルになる

アセンブラを見てみる

-S オプションをつけるとアセンブリのファイルを出力する

$ cc -O3 -S a.c
a.s
    .section    __TEXT,__text,regular,pure_instructions
    .globl  _main
    .align  4, 0x90
_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp2:
    .cfi_def_cfa_offset 16
Ltmp3:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp4:
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    leaq    L_.str(%rip), %rdi
    movl    $24, %esi
    movl    $0, -4(%rbp)
    movb    $0, %al
    callq   _printf
    movl    $0, %esi
    movl    %eax, -8(%rbp)          ## 4-byte Spill
    movl    %esi, %eax
    addq    $16, %rsp
    popq    %rbp
    retq
    .cfi_endproc

    .section    __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
    .asciz  "%d\n"


.subsections_via_symbols

コンパイラが、見事に全部 24 にして、不要な代入などは消去してるのが分かる。

最適化オプション -O3 では、変数も関数も名前もなくなってしまった。

gcc で同じ実験をする場合の注意

$ cc -c -O3 -S a.c

gcc は生成するファイルに元ファイルの情報を含むので、上の例は生成されるファイルには必ず差分が発生してしまう。
-S オプションをつけて、アセンブリの diff をとれば、ファイル名以外には相違ないことが確かめられる。

その子に伝えたいこと

  • -O3 オプションをつけたり、 inline キーワードをつけることで、コンパイラが頑張ってくれるので、人間が手で変数や関数を減らす必要はない。
  • 結果的に生成される実行ファイルが同じなら、変数や関数を減らすことは、「実行する機械資源の節約」にはならないよね。
  • それよりも、読みやすいスタイル・デバッグしやすいスタイルで書くことが「人間の労力を節約」になるんじゃないかな。
5
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5