Copyright© Takumi Vision Co.,Ltd.

実践Cプログラミング
Vol.1
製品開発部 加藤 天
[email protected]
Copyright© Takumi Vision Co.,Ltd.
1
目次
基本的な心得
 画像処理精度について
 高速化について
 その他C/C++言語特有の注意点

Copyright© Takumi Vision Co.,Ltd.
2
基本的な心得
可能な限り、コメントを書く
 意味のある関数名・変数名を使用する
 演算の順序は括弧付きで明示する
 外部変数の使用は必要最小限に

Copyright© Takumi Vision Co.,Ltd.
3
心得其1: コメントを書く(1)
他人(もしくは将来の自分)が見てわかりやす
いように、コメントは多量に書く。
 初めのうちは1行に1コメントがあっても良い。
 コメントにはセンスも大事です。


無いほうがマシなコメント
int

width;
// widthという名前のintの変数
あるとうれしいコメント
int
width;
Copyright© Takumi Vision Co.,Ltd.
// ラベリングで検出された領域の幅
4
心得其1: コメントを書く(2)

ファイルの先頭にはファイルの内容を、関数
の先頭には関数の処理内容や引数を書いて
おくと、読む人の負担が軽減されます。
プログラム開発は、集団作業になることが
ほとんどです。
よほどセンスの悪いコメントでない限り、
コメントの書き過ぎで困ることはありません。
Copyright© Takumi Vision Co.,Ltd.
5
心得其2: 意味のある関数・変数名を

関数名の悪い例
int
ImageConvA(
unsigned char *Image )
変換関数みたいだけど・・・なんの変換かわからない

まだマシな例
int
ImageConvRGB_YCC(
unsigned char *Image )
RGB→YCCの変換ということは判る
Copyright© Takumi Vision Co.,Ltd.
6
心得其2: 意味のある関数・変数名を

変数名の悪い例
int i, j, p, q;
char *Image1, *Image2;

まだマシな例
int width, height, LoopX, LoopY;
char *CamImage, *MaskedImage;
Copyright© Takumi Vision Co.,Ltd.
7
心得其3: 演算順序を明示する


演算順序は()で明示し、常に自分が意図した通り
の順序で演算が実行されるようにする(演算子の優
先順位を暗記する必要は無い)
演算の順序を明示しないマニアックなコードは、可
読性を損なう。また、間違いがあった場合にも、発見
が遅れてしまう
例
data = pix&0x03**p--;
↓
data = (pix&0x03)*(*(p--));
Copyright© Takumi Vision Co.,Ltd.
8
心得其4: 外部変数の使用は必要最
小限に
変数のスコープを意識し、外部変数はできる
だけ使用しない
 やむなく使用する場合には、必ずユニークな
変数名をつける、簡単な名前だと他の人と
被ったり、検索が大変になる。

Copyright© Takumi Vision Co.,Ltd.
9
画像処理精度について
幅・高さで表現された領域へのアクセスには
注意する
 浮動少数から整数への変換は切り捨てなの
で、四捨五入を忘れずに(変換前に0.5を足
す)

Copyright© Takumi Vision Co.,Ltd.
10
幅・高さで表現された領域へのアクセ
スには注意する

開始点(x、y)、幅W、高さHで表される四角形の右
下の座標(x’、y’)を求める場合
(x,y)
W
H
(x’,y’)
Copyright© Takumi Vision Co.,Ltd.
数式では
x’ = x + W
y’ = y + H
11
幅・高さで表現された領域へのアクセ
スには注意する

ところが実際の画像はピクセル単位なので
1 2 3 4 5 6 7 8
1
2
3
4
5
6
7
8
開始点(3,2) ,W=6, H=5とすると
右下の点は(8,6)となり
W
H
Copyright© Takumi Vision Co.,Ltd.
x’ = x + W - 1
y’ = y + H - 1
となる。
12
高速化について



巨大なループの中での条件分岐は避ける
巨大なループの中での関数呼び出しは避ける
C++ならinline関数、Cならマクロを活用する
浮動小数演算は可能な限り避ける
CPUが浮動小数演算能力を持っていないなら、
全廃のつもりで。
Copyright© Takumi Vision Co.,Ltd.
13
巨大なループの中での条件分岐は避ける
for( y=0; y < height; y++ ){
for( x=0; x < width; x++ ){
if( x == 0 || x == (width -1 ) )
dat[y][x] = 0;
if( y == 0 || y == (height-1 ) )
dat[y][x] = 0;
dat[y][x] = dat[y][x] * 2;
}
}
条件分岐は、条件判定のコードの実行が必要で、さらにCPUの命令
先読み機能を阻害することもあります。
巨大なループの中では、その小さな時間が重なって無視できない程大
きくなります。
ループの中に条件分岐が多数ある場合は、ロジックが適切でないこと
が多いので、ループの中の処理を見直して見ましょう。
Copyright© Takumi Vision Co.,Ltd.
14
巨大なループの中での条件分岐は避ける
memset( &dat[ 0 ], 0, width );
for( y=1; y < height-1; y++ ){
dat[ y ][ 0 ] = 0;
for( x=1; x < width-1; x++ ){
dat[ y ][ x ] = dat[ y ][ x ] * 2;
}
dat[ y ][ x ] = 0;
処理を見直すと、条件分岐
を消せることもある
}
memset( &dat[ y ], 0, width );
Copyright© Takumi Vision Co.,Ltd.
15
巨大なループの中での関数呼び出しは避
ける
関数呼び出しは、引数とリターンアドレスをメ
モリ(主にスタック)に記憶し、CALL命令を実
行する時間が必要。
 巨大なループ内で関数を呼び出しているなら、
inline関数(C++)、マクロ(C)を検討してみる。
 コード領域が大きくなるので注意すること

Copyright© Takumi Vision Co.,Ltd.
16
高速化Tips(1): 結果が整数となる計
算では、浮動小数演算を使用しない

Pentiumなどの浮動小数演算機能を実装す
るCPUでは浮動小数演算にそれほど時間は
かからなくなしましたが、依然として結果を整
数に変換する際に必要なコードが多くなりま
す。
Copyright© Takumi Vision Co.,Ltd.
17
高速化Tips(1): 結果が整数となる計
算では、浮動小数演算を使用しない

例 :整数を返す2つの関数を比較
 整数演算
int test1( int a, int b )
{
int ans;
ans = a * b;
return( ans );
}
 浮動小数演算
int test2( double a, double b )
{
int ans;
ans = (int)( a * b);
return( ans );
}
Copyright© Takumi Vision Co.,Ltd.
18
高速化Tips(1): 結果が整数となる計
算では、浮動小数演算を使用しない

例 :整数を返す2つの関数を比較
 整数演算(ans
004113EE
004113F1
004113F5
mov
imul
mov
Copyright© Takumi Vision Co.,Ltd.
= a * b;)の展開コード
eax,dword ptr [a]
eax,dword ptr [b]
dword ptr [ans],eax
19
高速化Tips(1): 結果が整数となる計
算では、浮動小数演算を使用しない

例 :整数を返す2つの関数を比較
 浮動小数演算(ans
0041142E
00411431
00411434
00411439
fld
fmul
call
mov
= (int)( a * b);)の展開コード
qword ptr [a]
qword ptr [b]
@ILT+220(__ftol2_sse) (4110E1h)
dword ptr [ans],eax
;サブルーチンコール
サブルーチンコール先のエントリテーブル
・
・
004110DC jmp
RaiseException (413778h)
004110E1 jmp
_ftol2_sse (411740h)
;ジャンプ命令
004110E6 jmp
_crt_debugger_hook (413760h)
・
・
ジャンプ先のコード
00411740 cmp
dword ptr [___sse2_available (4175C8h)],0
00411747 je
_ftol2 (411776h)
;この1行は実行されない
00411749 push
ebp
0041174A mov
ebp,esp
0041174C sub
esp,8
0041174F and
esp,0FFFFFFF8h
00411752 fstp
qword ptr [esp]
00411755 cvttsd2si
eax,mmword ptr [esp]
0041175A leave
0041175B ret
;リターン
Copyright© Takumi Vision Co.,Ltd.
20
高速化Tips(1): 結果が整数となる計
算では、浮動小数演算を使用しない

例 :整数を返す2つの関数を比較
 整数演算はアセンブラコード3行で完結している
が、浮動小数演算ではサブルーチンコールも含
めて14行実行される。
 演算結果が整数で要求される処理では、浮動小
数データは可能な限り使用しないことが望ましい。
Copyright© Takumi Vision Co.,Ltd.
21
高速化Tips(2):整数演算中の直接数
値には、小数表現を使用しない

整数演算の中で、直接数値を小数表現で記
述した場合も、勝手に浮動小数演算に拡張し
て実行されるので注意が必要です
Copyright© Takumi Vision Co.,Ltd.
22
高速化Tips(2):整数演算中の直接数
値には、小数表現を使用しない

例1
 C言語記述
int test1( int a, int b )
{
int ans;
ans = a * 10 * b;
return( ans );
}
 ans
= a * 10 * b; の展開コード
004113EE
004113F1
004113F4
004113F8
mov
imul
imul
mov
Copyright© Takumi Vision Co.,Ltd.
eax,dword ptr [a]
eax,eax,0Ah
eax,dword ptr [b]
dword ptr [ans],eax
23
高速化Tips(2):整数演算中の直接数
値には、小数表現を使用しない

例2
 C言語記述
int test2( int a, int b )
{
int ans;
ans = a * 10.0 * b;
return( ans );
}
 ans
0041142E
00411431
00411437
0041143A
0041143F
= a * 10.0 * b; の展開コード
fild
fmul
fimul
call
mov
dword ptr [a]
qword ptr [__real@4024000000000000 (415740h)]
dword ptr [b]
@ILT+225(__ftol2_sse) (4110E6h)←サブルーチンコール
dword ptr [ans],eax
Copyright© Takumi Vision Co.,Ltd.
24
高速化Tips(3): 浮動小数演算回避法

定数に小数点を使用する場合
小数点以下が消えるようにスケールを操作し
て計算し、最後に除算でスケールを戻すこと
により、整数演算に置き換えることが可能
Copyright© Takumi Vision Co.,Ltd.
25
高速化Tips(3): 浮動小数演算回避法

浮動小数演算を含む関数の比較
例
: RGBからYを求める関数
int test1( int R, int G, int B )
{
int Y;
Y = (int)(0.299 * R + 0.587 * G + 0.114 * B);
return( Y );
}
Copyright© Takumi Vision Co.,Ltd.
26
高速化Tips(3): 浮動小数演算回避法

浮動小数演算を含む関数の比較
 そのまま使った場合
Y = (int)(0.299 * R + 0.587 * G + 0.114 * B);
00411BDE
00411BE1
00411BE7
00411BEA
00411BF0
00411BF2
00411BF5
00411BFB
00411BFD
00411C02
fild
fmul
fild
fmul
faddp
fild
fmul
faddp
call
mov
Copyright© Takumi Vision Co.,Ltd.
dword ptr [R]
qword ptr [__real@3fd322d0e5604189 (415748h)]
dword ptr [G]
qword ptr [__real@4008000000000000 (415750h)]
st(1),st
dword ptr [B]
qword ptr [__real@3fbd2f1a9fbe76c9 (415740h)]
st(1),st
@ILT+220(__ftol2_sse) (4110E1h)
dword ptr [Y],eax
27
高速化Tips(3): 浮動小数演算回避法

浮動小数演算を含む関数の比較
 1000倍することで実数を整数にし、最後に結果を1000で
割った場合
Y = ( 299 * R + 587 * G + 114 * B) / 1000;
00411BDE
00411BE1
00411BE7
00411BEA
00411BF0
00411BF2
00411BF5
00411BF8
00411BFA
00411BFB
00411C00
00411C02
mov
imul
mov
imul
add
mov
imul
add
cdq
mov
idiv
mov
eax,dword ptr [R]
eax,eax,12Bh
ecx,dword ptr [G]
ecx,ecx,24Bh
eax,ecx
edx,dword ptr [B]
edx,edx,72h
eax,edx
;除算の前準備
ecx,3E8h
eax,ecx
dword ptr [Y],eax
Copyright© Takumi Vision Co.,Ltd.
除算に3行必要
;除算命令
28
高速化Tips(3): 浮動小数演算回避法

浮動小数演算を含む関数の比較
 1024倍することで実数を整数にし、最後に結果を10ビット
右シフトした場合(=1024で割る)
Y = ( 306 * R + 601 * G + 117 * B) >>10;
00411BDE
00411BE1
00411BE7
00411BEA
00411BF0
00411BF2
00411BF5
00411BF8
00411BFA
mov
imul
mov
imul
add
mov
imul
add
sar
eax,dword ptr [R]
eax,eax,132h
ecx,dword ptr [G]
ecx,ecx,259h
eax,ecx
edx,dword ptr [B]
edx,edx,75h
eax,edx
eax,0Ah
00411BFD
mov
dword ptr [Y],eax
Copyright© Takumi Vision Co.,Ltd.
;符号つき10ビット右シフト→結果は1024の除算と等しいが1行で済む
29
高速化Tips(3): 浮動小数演算回避法

浮動小数演算を含む関数の比較
 測定結果
1000回の実行時間(ms)
浮動小数点
0.00131422
1000倍
0.00101326
1024倍
0.00094303
Copyright© Takumi Vision Co.,Ltd.
30
高速化Tips(3): 浮動小数演算回避法

浮動小数演算を含む関数の比較

考察
1024倍では、最後の1024の除算が単純な10ビット右シフトに
置き換えられることで1000倍した場合より高速になる。
但し、1000倍に比べ、
1.
2.
3.
可読性が落ちる
小数点以下の四捨五入が必要となる
わずかに桁あふれの天井に近づく
などの条件を考慮して、1000倍にするか1024倍にするかを
決定することが望ましい。
Copyright© Takumi Vision Co.,Ltd.
31
その他、C/C++特有の注意点



コンパイル時のワーニングは残さない
安易なキャスティングは避ける
条件分岐の判定部分には、実行が必須な
複数の処理を書かない
Copyright© Takumi Vision Co.,Ltd.
32
コンパイル時のワーニングは残さない

ワーニングは、曖昧に記述されたコードが意図しない
動作をする可能性があることを教えてくれるもの。エ
ラーじゃないから・・・と放置するのは大変危険です、
全部解決するようにしましょう。
Copyright© Takumi Vision Co.,Ltd.
33
安易なキャスティングは避ける

コンパイル時に、データ型の不一致によるエラーや
ワーニングが大量に出た時、キャストを付けて強引
に型を合わせることは、せっかくコンパイラが教えて
くれているバグを潜在化させる。

キャストを付ける前に、変数や引数の型を見直して
整合させることを考える。
Copyright© Takumi Vision Co.,Ltd.
34
その他(1):条件分岐の判定



コンパイル環境によって、条件分岐の判定部分は期
待した通りにコンパイルされないことがある
if文の中に複数の条件が記述されている場合、最初
の条件で条件分岐の真偽が決定すると、以降の条
件は判定(実行)されない
コンパイラを速度優先で最適化させたときに多く見
られ、発生すると発見が困難なトラブルの一つ
Copyright© Takumi Vision Co.,Ltd.
35
その他(1):条件分岐の判定
例 画像データにfilter1( )とfilter2( )の処理を施し、
それぞれの返り値が-1以外・5以外という条件を
満たしたときだけfilter3( )の処理を行う
void example( char *Image )
{
if( ( filter1( Image ) != -1 ) && ( filter2( Image ) != 5 ) ){
filter3( Image );
}
return;
}
このように記述すると、期待通りにコンパイルされないことがある
Copyright© Takumi Vision Co.,Ltd.
36
その他(1):条件分岐の判定
コンパイルでの展開 (VisualStudio2005 速度優先で最適化)
if( ( filter1( Image ) != -1 ) && ( filter2( Image ) != 5 ) ){
mov
esi, DWORD PTR _Image$[esp]
push
esi
call
?filter1@@YAHPAD@Z ; filter1の呼び出し
add
esp, 4
cmp
eax, -1
; eaxレジスタに格納されたfilter1の返り値と-1を比較
je
SHORT $LN1@example ; 上の比較で値が等しかったらLN1@exampleにジャンプする
push
esi
call
?filter2@@YAHPAD@Z ; filter2の呼び出しはココ
add
esp, 4
cmp
eax, 5
je
SHORT $LN1@example
filter3( Image );
push
esi
call
?filter3@@YAXPAD@Z ; filter3
add
esp, 4
$LN1@example:
pop
esi
}
return;
ret
0
Copyright© Takumi Vision Co.,Ltd.
37
独創的なコードはC/C++の特性を知り尽くしてから
void example( char *Image )
{
int fil1_ret;
int fil2_ret;
fil1_ret = filter1( Image );
fil2_ret = filter2( Image );
if( ( fil1_ret != -1 ) && ( fil2_ret != 5 ) )
{
filter3( Image );
}
return;
}
もし独創的なコードの記述方法を思いついても、それを安易に試
すのは危険です、そのためにはコンパイラの特性を熟知している
必要がありますし、コンパイラが変わったら動かなくなる可能性が
あります、素直なコードが最も開発効率が良いものです。
Copyright© Takumi Vision Co.,Ltd.
38
御清聴、ありがとうございました。
Copyright© Takumi Vision Co.,Ltd.
39