レクサーを試していますが、プログラムの一部でwhileループからifステートメントとdo-whileループに切り替えると、コードが最大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:
文字「a」のみで構成されるメガバイトのテキストに対してこれらのアセンブリスニペットだけを実行すると、高速コードは30%高速になります。私の推測では、ブランチの予測ミスのためにスローコードは遅いと思いますが、ループでは1回限りのコストになると思いました。
両方のスニペットをテストするために使用したプログラムは次のとおりです。
#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