Tối Ưu Hóa Cho Memcpy Trong VC
Trong nhiều trình biên dịch, hàm memcpy được coi là một hàm nội tại (intrinsic function) - nghĩa là do chính trình biên dịch cài đặt. Điều này cho phép nó được tối ưu hóa hiệu quả hơn so với các hàm inline thông thường. Trình biên dịch có thể tạo ra nhiều phiên bản khác nhau của hàm memcpy dựa trên việc tham số truyền vào là hằng số hay biến số, từ đó đạt được hiệu suất tối ưu nhất. Đây là điều mà các kỹ thuật như hàm inline hay mẫu hàm (template) không thể thực hiện được.
Hãy cùng phân tích cách trình biên dịch Visual C++ (phiên bản VC6) tối ưu hóa hàm memcpy:
Với đoạn mã sau: void foo(void *d, const void *s) { memcpy(d, s, 1); }
Khi chọn chế độ tối ưu hóa hiệu năng, mã hợp ngữ được sinh ra là: mov eax, DWORD PTR _s$[esp-4] mov edx, DWORD PTR _d$[esp-4] mov cl, BYTE PTR [eax] mov BYTE PTR [edx], cl
Chỉ cần một byte được sao chép thông qua thanh ghi cl. Khi thay đổi kích thước từ 1 thành 4 byte: mov eax, DWORD PTR _s$[esp-4] mov edx, DWORD PTR _d$[esp-4] mov ecx, DWORD PTR [eax] mov DWORD PTR [edx], ecx
Lúc này trình biên dịch sử dụng lệnh mov thông thường để xử lý từ 4 byte. Với độ dài 8 byte: mov eax, DWORD PTR _s$[esp-4] mov ecx, DWORD PTR _d$[esp-4] mov edx, DWORD PTR [eax] mov DWORD PTR [ecx], edx mov eax, DWORD PTR [eax+4] mov DWORD PTR [ecx+4], eax
Hai lệnh mov liên tiếp được sử dụng để xử lý 8 byte dữ liệu. Thậm chí với kích thước 19 byte (không phải bội số của 4): mov eax, DWORD PTR _s$[esp-4] mov ecx, DWORD PTR _d$[esp-4] mov edx, DWORD PTR [eax] mov DWORD PTR [ecx], edx mov edx, DWORD PTR [eax+4] mov DWORD PTR [ecx+4], edx mov edx, DWORD PTR [eax+8] mov DWORD PTR [ecx+8], edx mov edx, DWORD PTR [eax+12] mov DWORD PTR [ecx+12], edx mov dx, WORD PTR [eax+16] mov WORD PTR [ecx+16], dx mov al, BYTE PTR [eax+18] mov BYTE PTR [ecx+18], al
Khi kích thước đạt đến 20 byte, trình biên dịch chuyển sang sử dụng lệnh chuỗi rep movsd: push esi mov esi, DWORD PTR _s$[esp] push edi mov edi, DWORD PTR _d$[esp+4] mov ecx, 5 rep movsd pop edi pop esi
Nếu kích thước không phải là bội số của 4, ví dụ 23 byte: push esi mov esi, DWORD PTR _s$[esp] push edi mov edi, DWORD PTR _d$[esp+4] mov ecx, 5 rep movsd movsw movsb pop edi pop esi
Trình biên dịch thông minh thêm các lệnh movsw và movsb để xử lý phần dư.
Khi kích thước là biến số: void foo(void *d, const void *s, size_t size) { memcpy(d, s, size); }
Mã hợp ngữ được tối ưu như sau: mov ecx, DWORD PTR _size$[esp-4] push esi mov esi, DWORD PTR _s$[esp] mov eax, ecx push edi mov edi, DWORD PTR _d$[esp+4] shr ecx, 2 rep movsd mov ecx, eax and ecx, 3 rep movsb pop edi pop esi
Vì không biết trước kích thước có phải bội số của 4 hay không, trình biên dịch thêm đoạn mã xử lý phần dư bằng lệnh and ecx,3 và rep movsb.
Câu hỏi đặt ra: Liệu có cách nào thông báo cho trình biên dịch rằng kích thước cần sao chép luôn là bội số của 4? Câu trả lời là hoàn toàn có thể! Khi sử dụng memcpy(d, s, size*4), mã hợp ngữ được tối ưu tối giản: mov ecx, DWORD PTR _size$[esp-4] push esi mov esi, DWORD PTR _s$[esp] push edi mov edi, DWORD PTR _d$[esp+4] rep movsd pop edi pop esi
Đây chính là minh chứng cho khả năng tối ưu hóa xuất sắc của trình biên dịch khi có thêm thông tin về ngữ cảnh sử dụng hàm memcpy.