나는 렉서 (lexer)를 실험하고 있는데, 프로그램의 한 부분에서 while-loop에서 if-statement와 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:
나는 텍스트는 문자 구성의 메가 바이트에 불과 이러한 조립 미리보기를 실행하면 'A', 빠른 코드 30 % 빨라집니다. 내 생각 엔 느린 코드는 분기 오보 (misprediction)로 인해 속도가 느려지지만 루프에서 한 번만 비용이들 것이라고 생각했습니다.
이#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
그게 * 미친 짓이야 - 컴파일러가 스스로하는 일은 매우 일반적인 최적화입니다. 빠른 속도의 이유는 빠른 코드에서 이터 레이션 당 하나의 점프가 적고 점프의 처리량이 제한적일뿐입니다. – harold
퍼포먼스 기반 레지스터 프로파일 러는 아마 가장 좋은 답을 얻을 것입니다. 그러나 명백한 점프와는 별도로, 코드 캐쉬에 더 잘 부합하기 때문에 코드의 두 번째 비트가 더 빠르다는 것을 추측합니다 (페치 - 앤 - 디코 드되지만이 오버 헤드는 여기서는 의미가 없습니다). 점프 대상 정렬도 또 다른 요소 일 수 있지만 주소가 없으면 여기에 말하기가 어렵습니다. – Necrolis
브라이언, CPU는 무엇입니까? http://www.agner.org/optimize/보세요 harold, static jmps는 x86 (Atom이 아닌) 최신 x86 CPU에서 항상 수행되는 것으로 예측되므로 비용이 들지 않습니다. – osgx