内部ループを持つ単純な関数があります。入力値をスケーリングし、ルックアップ テーブルで出力値を検索し、それを宛先にコピーします。(ftol_ambient は、float から int への高速変換のために Web からコピーしたトリックです)。
for (i = 0; i < iCount; ++i)
{
iScaled = ftol_ambient(*pSource * PRECISION3);
if (iScaled <= 0)
*pDestination = 0;
else if (iScaled >= PRECISION3)
*pDestination = 255;
else
{
iSRGB = FloatToSRGBTable3[iScaled];
*pDestination = iSRGB;
}
pSource++;
pDestination++;
}
現在、私のルックアップ テーブルは有限であり、フロートは無限であるため、オフ バイ ワン エラーが発生する可能性があります。その場合を処理するためのコードを使用して、関数のコピーを作成しました。唯一の違いは 2 行のコードが追加されていることに注意してください。見苦しいポインターのキャストは無視してください。
for (i = 0; i < iCount; ++i)
{
iScaled = ftol_ambient(*pSource * PRECISION3);
if (iScaled <= 0)
*pDestination = 0;
else if (iScaled >= PRECISION3)
*pDestination = 255;
else
{
iSRGB = FloatToSRGBTable3[iScaled];
if (((int *)SRGBCeiling)[iSRGB] <= *((int *)pSource))
++iSRGB;
*pDestination = (unsigned char) iSRGB;
}
pSource++;
pDestination++;
}
ここが奇妙な部分です。100000 要素の同一の入力を 100 回繰り返して、両方のバージョンをテストしています。私の Athlon 64 1.8 GHz (32 ビット モード) では、最初の関数に 0.231 秒かかり、2 番目の (長い) 関数に 0.185 秒かかります。両方の関数が同じソース ファイル内で隣接しているため、コンパイラ設定が異なる可能性はありません。テストを実行する順序を逆にして何度もテストを実行しましたが、タイミングは毎回ほぼ同じです。
最新のプロセッサには多くの謎があることは知っていますが、どうしてこれが可能になるのでしょうか?
比較のために、Microsoft VC++6 コンパイラからの関連するアセンブラ出力を次に示します。
; 173 : for (i = 0; i < iCount; ++i)
$L4455:
; 174 : {
; 175 : iScaled = ftol_ambient(*pSource * PRECISION3);
fld DWORD PTR [esi]
fmul DWORD PTR __real@4@400b8000000000000000
fstp QWORD PTR $T5011[ebp]
; 170 : int i;
; 171 : int iScaled;
; 172 : unsigned int iSRGB;
fld QWORD PTR $T5011[ebp]
; 173 : for (i = 0; i < iCount; ++i)
fistp DWORD PTR _i$5009[ebp]
; 176 : if (iScaled <= 0)
mov edx, DWORD PTR _i$5009[ebp]
test edx, edx
jg SHORT $L4458
; 177 : *pDestination = 0;
mov BYTE PTR [ecx], 0
; 178 : else if (iScaled >= PRECISION3)
jmp SHORT $L4461
$L4458:
cmp edx, 4096 ; 00001000H
jl SHORT $L4460
; 179 : *pDestination = 255;
mov BYTE PTR [ecx], 255 ; 000000ffH
; 180 : else
jmp SHORT $L4461
$L4460:
; 181 : {
; 182 : iSRGB = FloatToSRGBTable3[iScaled];
; 183 : *pDestination = (unsigned char) iSRGB;
mov dl, BYTE PTR _FloatToSRGBTable3[edx]
mov BYTE PTR [ecx], dl
$L4461:
; 184 : }
; 185 : pSource++;
add esi, 4
; 186 : pDestination++;
inc ecx
dec edi
jne SHORT $L4455
$L4472:
; 199 : {
; 200 : iScaled = ftol_ambient(*pSource * PRECISION3);
fld DWORD PTR [esi]
fmul DWORD PTR __real@4@400b8000000000000000
fstp QWORD PTR $T4865[ebp]
; 195 : int i;
; 196 : int iScaled;
; 197 : unsigned int iSRGB;
fld QWORD PTR $T4865[ebp]
; 198 : for (i = 0; i < iCount; ++i)
fistp DWORD PTR _i$4863[ebp]
; 201 : if (iScaled <= 0)
mov edx, DWORD PTR _i$4863[ebp]
test edx, edx
jg SHORT $L4475
; 202 : *pDestination = 0;
mov BYTE PTR [edi], 0
; 203 : else if (iScaled >= PRECISION3)
jmp SHORT $L4478
$L4475:
cmp edx, 4096 ; 00001000H
jl SHORT $L4477
; 204 : *pDestination = 255;
mov BYTE PTR [edi], 255 ; 000000ffH
; 205 : else
jmp SHORT $L4478
$L4477:
; 206 : {
; 207 : iSRGB = FloatToSRGBTable3[iScaled];
xor ecx, ecx
mov cl, BYTE PTR _FloatToSRGBTable3[edx]
; 208 : if (((int *)SRGBCeiling)[iSRGB] <= *((int *)pSource))
mov edx, DWORD PTR _SRGBCeiling[ecx*4]
cmp edx, DWORD PTR [esi]
jg SHORT $L4481
; 209 : ++iSRGB;
inc ecx
$L4481:
; 210 : *pDestination = (unsigned char) iSRGB;
mov BYTE PTR [edi], cl
$L4478:
; 211 : }
; 212 : pSource++;
add esi, 4
; 213 : pDestination++;
inc edi
dec eax
jne SHORT $L4472
編集: Nils Pipenbrinck の仮説をテストしようとして、最初の関数のループの前と内側に数行追加しました。
int one = 1;
int two = 2;
if (one == two)
++iSRGB;
最初の関数の実行時間は 0.152 秒に短縮されました。面白い。
編集 2: Nils は、比較はリリース ビルドから最適化されると指摘しましたが、実際にそうです。アセンブリ コードの変更は非常に微妙です。ここに投稿して、手掛かりが得られるかどうかを確認します。この時点で、コードの配置であるかどうか疑問に思っていますか?
; 175 : for (i = 0; i < iCount; ++i)
$L4457:
; 176 : {
; 177 : iScaled = ftol_ambient(*pSource * PRECISION3);
fld DWORD PTR [edi]
fmul DWORD PTR __real@4@400b8000000000000000
fstp QWORD PTR $T5014[ebp]
; 170 : int i;
; 171 : int iScaled;
; 172 : int one = 1;
fld QWORD PTR $T5014[ebp]
; 173 : int two = 2;
fistp DWORD PTR _i$5012[ebp]
; 178 : if (iScaled <= 0)
mov esi, DWORD PTR _i$5012[ebp]
test esi, esi
jg SHORT $L4460
; 179 : *pDestination = 0;
mov BYTE PTR [edx], 0
; 180 : else if (iScaled >= PRECISION3)
jmp SHORT $L4463
$L4460:
cmp esi, 4096 ; 00001000H
jl SHORT $L4462
; 181 : *pDestination = 255;
mov BYTE PTR [edx], 255 ; 000000ffH
; 182 : else
jmp SHORT $L4463
$L4462:
; 183 : {
; 184 : iSRGB = FloatToSRGBTable3[iScaled];
xor ecx, ecx
mov cl, BYTE PTR _FloatToSRGBTable3[esi]
; 185 : if (one == two)
; 186 : ++iSRGB;
; 187 : *pDestination = (unsigned char) iSRGB;
mov BYTE PTR [edx], cl
$L4463:
; 188 : }
; 189 : pSource++;
add edi, 4
; 190 : pDestination++;
inc edx
dec eax
jne SHORT $L4457