私はレクサーを試していますが、プログラムのある部分でwhileループからif文とdo-while-loopに切り替えると、コードが20%高速になり、狂ったようです。コンパイラ生成コードの違いをこれらのアセンブリスニペットに分離しました。誰かがなぜ速いコードがより速いのか知っていますか?なぜこのアセンブリコードは高速ですか?
'edi'は現在のテキスト位置、 'ebx'はテキストの最後、 'isAlpha'は文字がアルファベットの場合は1、そうでない場合は0を持つルックアップテーブルです。
遅いコード:
slow_loop:
00401897 cmp edi,ebx
00401899 je slow_done (4018AAh)
0040189B movzx eax,byte ptr [edi]
0040189E cmp byte ptr isAlpha (4533E0h)[eax],0
004018A5 je slow_done (4018AAh)
004018A7 inc edi
004018A8 jmp slow_loop (401897h)
slow_done:
速いコード:
fast_loop:
0040193D inc edi
0040193E cmp edi,ebx
00401940 je fast_done (40194Eh)
00401942 movzx eax,byte ptr [edi]
00401945 cmp byte ptr isAlpha (4533E0h)[eax],0
0040194C jne fast_loop (40193Dh)
fast_done:
私は、テキストのみの文字からなるのメガバイトに対してだけでこれらのアセンブリのスニペットを実行する場合「」、高速なコード30%高速です。私の推測では、低速なコードは分岐の誤予測のために遅いですが、私はループで一度のコストと思っていました。
ここで私は両方のスニペットをテストするために使用するプログラムがあります:テストプログラムの
#include <Windows.h>
#include <string>
#include <iostream>
int main(int argc, char* argv[])
{
static char isAlpha[256];
for (int i = 0; i < sizeof(isAlpha); ++i)
isAlpha[i] = isalpha(i) ? 1 : 0;
std::string test(1024*1024, 'a');
const char* start = test.c_str();
const char* limit = test.c_str() + test.size();
DWORD slowStart = GetTickCount();
for (int i = 0; i < 10000; ++i)
{
__asm
{
mov edi, start
mov ebx, limit
inc edi
slow_loop:
cmp edi,ebx
je slow_done
movzx eax,byte ptr [edi]
cmp byte ptr isAlpha [eax],0
je slow_done
inc edi
jmp slow_loop
slow_done:
}
}
DWORD slowEnd = GetTickCount();
std::cout << "slow in " << (slowEnd - slowStart) << " ticks" << std::endl;
DWORD fastStart = GetTickCount();
for (int i = 0; i < 10000; ++i)
{
__asm
{
mov edi, start
mov ebx, limit
fast_loop:
inc edi
cmp edi,ebx
je fast_done
movzx eax,byte ptr [edi]
cmp byte ptr isAlpha [eax],0
jne fast_loop
fast_done:
}
}
DWORD fastEnd = GetTickCount();
std::cout << "fast in " << (fastEnd - fastStart) << " ticks" << std::endl;
return 0;
}
出力は、あなたのテストテキストは、ループが一人ひとりのために最後まで実行する原因となる
slow in 8455 ticks
fast in 5694 ticks
これは*夢中です - それはコンパイラが単独で行うのが非常に一般的な最適化です。高速化が進んでいる理由は、高速コードでは1回の反復でジャンプが少なく、ジャンプのスループットが限られているだけです。 – harold
パフォーマンスベースのレジスタプロファイラーがおそらく最高の答えを出すでしょうが、明らかなジャンプとは別に、コードキャッシュの方が優れているため、コードの2番目のビットが速いと推測しています(フェッチ/デコードしますが、そのオーバーヘッドはここでは意味がありません)。ジャンプターゲットのアライメントもまた別の要因かもしれませんが、アドレスなしでここに伝えるのは難しいです。 – Necrolis
ブライアン、あなたのCPUは何ですか? http://www.agner.org/optimize/を見てください。また、harold、static jmpsは、現代(非Atom)x86 CPU上で常に行われると予測されているため、コストはかかりません。 – osgx