Почему этот ассемблерный код быстрее?

Я экспериментировал с лексером и обнаружил, что переключение с цикла 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
13
задан Mysticial 1 July 2012 в 21:38
поделиться