実践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
© Copyright 2025 ExpyDoc