Я экспериментировал с лексером и обнаружил, что переключение с цикла 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:
Если я запускаю только эти фрагменты сборки для мегабайта текста, состоящего только из буквы «а», быстрый код работает на 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