Lý lịch:
Trong khi tối ưu hóa một số mã Pascal với ngôn ngữ lắp ráp nhúng, tôi nhận thấy một MOV
lệnh không cần thiết và đã loại bỏ nó.
Thật ngạc nhiên, loại bỏ các hướng dẫn không cần thiết khiến chương trình của tôi bị chậm lại .
Tôi thấy rằng việc thêm các MOV
hướng dẫn tùy ý, vô dụng làm tăng hiệu suất hơn nữa.
Hiệu ứng là thất thường và thay đổi dựa trên thứ tự thực hiện: các lệnh rác tương tự được chuyển lên hoặc xuống bởi một dòng duy nhất tạo ra sự chậm lại .
Tôi hiểu rằng CPU thực hiện tất cả các loại tối ưu hóa và hợp lý hóa, nhưng, điều này có vẻ giống như ma thuật đen.
Dữ liệu:
Một phiên bản mã của tôi có điều kiện biên dịch ba hoạt động rác ở giữa một vòng lặp chạy 2**20==1048576
thời gian. (Chương trình xung quanh chỉ tính băm SHA-256 ).
Kết quả trên máy khá cũ của tôi (Intel (R) Core (TM) 2 CPU 6400 @ 2.13 GHz):
avg time (ms) with -dJUNKOPS: 1822.84 ms
avg time (ms) without: 1836.44 ms
Các chương trình được chạy 25 lần trong một vòng lặp, với thứ tự chạy thay đổi ngẫu nhiên mỗi lần.
Trích đoạn:
{$asmmode intel}
procedure example_junkop_in_sha256;
var s1, t2 : uint32;
begin
// Here are parts of the SHA-256 algorithm, in Pascal:
// s0 {r10d} := ror(a, 2) xor ror(a, 13) xor ror(a, 22)
// s1 {r11d} := ror(e, 6) xor ror(e, 11) xor ror(e, 25)
// Here is how I translated them (side by side to show symmetry):
asm
MOV r8d, a ; MOV r9d, e
ROR r8d, 2 ; ROR r9d, 6
MOV r10d, r8d ; MOV r11d, r9d
ROR r8d, 11 {13 total} ; ROR r9d, 5 {11 total}
XOR r10d, r8d ; XOR r11d, r9d
ROR r8d, 9 {22 total} ; ROR r9d, 14 {25 total}
XOR r10d, r8d ; XOR r11d, r9d
// Here is the extraneous operation that I removed, causing a speedup
// s1 is the uint32 variable declared at the start of the Pascal code.
//
// I had cleaned up the code, so I no longer needed this variable, and
// could just leave the value sitting in the r11d register until I needed
// it again later.
//
// Since copying to RAM seemed like a waste, I removed the instruction,
// only to discover that the code ran slower without it.
{$IFDEF JUNKOPS}
MOV s1, r11d
{$ENDIF}
// The next part of the code just moves on to another part of SHA-256,
// maj { r12d } := (a and b) xor (a and c) xor (b and c)
mov r8d, a
mov r9d, b
mov r13d, r9d // Set aside a copy of b
and r9d, r8d
mov r12d, c
and r8d, r12d { a and c }
xor r9d, r8d
and r12d, r13d { c and b }
xor r12d, r9d
// Copying the calculated value to the same s1 variable is another speedup.
// As far as I can tell, it doesn't actually matter what register is copied,
// but moving this line up or down makes a huge difference.
{$IFDEF JUNKOPS}
MOV s1, r9d // after mov r12d, c
{$ENDIF}
// And here is where the two calculated values above are actually used:
// T2 {r12d} := S0 {r10d} + Maj {r12d};
ADD r12d, r10d
MOV T2, r12d
end
end;
Hãy tự thử:
Mã này đang trực tuyến tại GitHub nếu bạn muốn tự mình dùng thử.
Những câu hỏi của tôi:
- Tại sao sao chép vô dụng nội dung của người đăng ký vào RAM sẽ tăng hiệu suất?
- Tại sao cùng một hướng dẫn vô dụng cung cấp một sự tăng tốc trên một số dòng và làm chậm lại những người khác?
- Là hành vi này một cái gì đó có thể được khai thác dự đoán bởi một trình biên dịch?