Tôi đang thử nghiệm với một lexer, và tôi thấy rằng việc chuyển từ một vòng lặp while sang câu lệnh if và một vòng lặp while trong một phần của chương trình dẫn đến mã nhanh hơn ~ 20%, điều này có vẻ điên rồ. Tôi đã cô lập sự khác biệt trong trình biên dịch tạo ra mã cho các đoạn mã lắp ráp này. Có ai biết tại sao mã nhanh nhanh hơn không?Tại sao mã lắp ráp này nhanh hơn?
Trong assembly, 'edi' là vị trí văn bản hiện tại, 'ebx' là phần cuối của văn bản và 'isAlpha' là bảng tra cứu có 1 nếu ký tự là chữ cái và 0 nếu không.
Mã chậm:
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:
Mã nhanh:
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:
Nếu tôi chỉ chạy những đoạn lắp ráp chống lại một megabyte của văn bản bao gồm duy nhất của chữ 'a', mã nhanh nhanh hơn 30%. Đoán của tôi là mã chậm chạp chậm vì lỗi chi nhánh, nhưng tôi nghĩ trong một vòng lặp là chi phí một lần.
Đây là chương trình mà tôi sử dụng để kiểm tra cả hai đoạn:
#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;
}
Đầu ra của chương trình thử nghiệm là
slow in 8455 ticks
fast in 5694 ticks
Điều đó * là * điên - đó là một sự tối ưu hóa rất phổ biến cho các trình biên dịch tự làm. Đối với lý do tại sao nó nhanh hơn, có một ít nhảy cho mỗi lần lặp lại trong mã nhanh, và nhảy chỉ có một thông lượng giới hạn. – harold
trình biên dịch đăng ký dựa trên hiệu suất có thể mang lại câu trả lời tốt nhất, nhưng ngoài bước nhảy rõ ràng, tôi đoán bit thứ hai nhanh hơn vì nó phù hợp hơn vào bộ đệm mã (cũng có vài byte để tìm nạp và giải mã, nhưng đó là vô nghĩa ở đây). liên kết mục tiêu nhảy cũng có thể là một yếu tố khác, nhưng điều đó khó nói ở đây mà không có địa chỉ – Necrolis
Brian, CPU của bạn là gì? Hãy xem http://www.agner.org/optimize/ Và harold, các jmps tĩnh được dự đoán là luôn được thực hiện trên các CPU x86 hiện đại (không phải Atom), vì vậy chúng không tốn chi phí. – osgx