組込み開発において、リファクタリングしようとすると、自分は怒られる。
- パフォーマンスが低下する
- スタックオーバーフローする
怒られるのが嫌で、リファクタリングできない。この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年前の常識は、現代の非常識。
ただし、スタックオーバーフローは注意を払う。