組込み開発において、リファクタリングしようとすると、自分は怒られる。

  • パフォーマンスが低下する
  • スタックオーバーフローする

怒られるのが嫌で、リファクタリングできない。この2大迷信について、簡単な実験してみた。

Normalコード(Test1)

これは普通のコード。ここを参考にした。clock

[tsu-nera]% cat timer.c
#include <time.h>
#include <stdio.h>

int main(void)
{
  clock_t start, end;
  long l;
  long i=0;
  int  n=0;
  clock_t total_start, total_end;

  total_start = clock();

  while(n < 5){

    start = clock();
    i = 0;

    for (l=0; l<100000000; l++) {
      i++;
    }

    end = clock();
    printf("ループ1億回の時間: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
    n++;
  }

  total_end = clock();

  printf("平均の時間: %f秒\n", (double)(total_end - total_start) / CLOCKS_PER_SEC / n);

  return 0;

}

Functionコード(Test2)

つづいて、関数でインクリメントを抽出したコード。

[tsu-nera]% cat timer2.c
#include <time.h>
#include <stdio.h>


void incriment(long* i) {
  (*i)++;
}

int main(void)
{
  clock_t start, end;
  long l;
  long i=0;
  int  n=0;
  clock_t total_start, total_end;

  total_start = clock();

  while(n < 5){

    start = clock();
    i = 0;

    for (l=0; l<100000000; l++) {
      incriment(&i);
    }

    end = clock();
    printf("ループ1億回の時間: %f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
    n++;
  }

  total_end = clock();

  printf("平均の時間: %f秒\n", (double)(total_end - total_start) / CLOCKS_PER_SEC / n);

  return 0;

}

実験結果

気持ち、Test1のほうが早い気がする。もっと実験すれば、大数の法則で正確な値がでそうだけど、まあいいや。とりあえず、気持ちの問題で小さな関数はパフォーマンスをあまり低下させないという自身がついた。

Test1

[tsu-nera]% ./a.out
ループ1億回の時間: 1.050000秒
ループ1億回の時間: 0.720000秒
ループ1億回の時間: 0.670000秒
ループ1億回の時間: 1.050000秒
ループ1億回の時間: 1.170000秒
平均の時間: 0.932000秒
[tsu-nera]% ./a.out
ループ1億回の時間: 1.030000秒
ループ1億回の時間: 0.960000秒
ループ1億回の時間: 0.960000秒
ループ1億回の時間: 0.770000秒
ループ1億回の時間: 0.680000秒
平均の時間: 0.882000秒
[tsu-nera]% ./a.out
ループ1億回の時間: 1.250000秒
ループ1億回の時間: 0.840000秒
ループ1億回の時間: 0.670000秒
ループ1億回の時間: 0.570000秒
ループ1億回の時間: 0.630000秒
平均の時間: 0.792000秒
[tsu-nera]% ./a.out
ループ1億回の時間: 1.030000秒
ループ1億回の時間: 0.730000秒
ループ1億回の時間: 0.630000秒
ループ1億回の時間: 0.520000秒
ループ1億回の時間: 0.460000秒
平均の時間: 0.674000秒
[tsu-nera]% ./a.out
ループ1億回の時間: 0.940000秒
ループ1億回の時間: 0.720000秒
ループ1億回の時間: 0.630000秒
ループ1億回の時間: 0.540000秒
ループ1億回の時間: 0.500000秒
平均の時間: 0.666000秒

Test2

[tsu-nera]% ./a.out
ループ1億回の時間: 1.010000秒
ループ1億回の時間: 0.810000秒
ループ1億回の時間: 0.650000秒
ループ1億回の時間: 0.560000秒
ループ1億回の時間: 0.480000秒
平均の時間: 0.702000秒
[tsu-nera]% ./a.out
ループ1億回の時間: 1.470000秒
ループ1億回の時間: 1.160000秒
ループ1億回の時間: 0.930000秒
ループ1億回の時間: 0.700000秒
ループ1億回の時間: 0.590000秒
平均の時間: 0.970000秒
[tsu-nera]% ./a.out
ループ1億回の時間: 1.050000秒
ループ1億回の時間: 0.870000秒
ループ1億回の時間: 0.710000秒
ループ1億回の時間: 0.770000秒
ループ1億回の時間: 1.270000秒
平均の時間: 0.934000秒
[tsu-nera]% ./a.out
ループ1億回の時間: 1.200000秒
ループ1億回の時間: 0.890000秒
ループ1億回の時間: 0.710000秒
ループ1億回の時間: 0.650000秒
ループ1億回の時間: 0.540000秒
平均の時間: 0.798000秒
[tsu-nera]% ./a.out
ループ1億回の時間: 1.020000秒
ループ1億回の時間: 0.760000秒
ループ1億回の時間: 0.620000秒
ループ1億回の時間: 0.550000秒
ループ1億回の時間: 0.560000秒
平均の時間: 0.704000秒

スタックオーバーフローのテスト(Test3)

関数を呼びすぎるとスタックオーバーフローするよと脅されたが、脅した人はどのくらいの確信をもって発言したのかをテストした。

こんなサンプル。

#include <stdio.h>

void incriment(long* i) {
  (*i)++;
  printf("i=%d\n",*i);
  incriment(i);
}

int main(void)
{
  long  i=0;
  incriment(&i);
  return 0;

}

テスト結果

大体、自分のCygwin環境だと400000くらいでクラッシュする。

i=392880
i=392881
i=392882
[1]    18958 segmentation fault  ./a.out

ただ、スタックオーバーフローは、タスクサイズと環境依存なので、一概に安心はできないな。3階層くらいならば、へのようなものか(・・?

結論

関数抽出しても、コンパイラが最適化してくれるため、パフォーマンスは気にしない。小さな関数はコンパイラがinlineしてくれる。20年前の常識は、現代の非常識。

ただし、スタックオーバーフローは注意を払う。