Tôi đã thực hiện một số cấu hình sau: Máy thử nghiệm (AMD Athlon64 x2 3800+) được khởi động, chuyển sang chế độ dài (ngắt ngắt) và lệnh quan tâm được thực hiện trong vòng lặp, 100 lần lặp lại và 1.000 chu kỳ vòng lặp. Cơ thể vòng lặp được căn chỉnh đến 16 byte. Thời gian được đo bằng lệnh rdtsc trước và sau vòng lặp. Ngoài ra một vòng lặp giả mà không có bất kỳ lệnh nào được thực thi (đo 2 chu kỳ trên mỗi vòng lặp và 14 chu kỳ cho phần còn lại) và kết quả được trừ ra khỏi kết quả của thời gian lược tả lệnh.
Các hướng dẫn sau đây được đo:
- "
lock cmpxchg [rsp - 8], rdx
" (cả với trận đấu so sánh và không phù hợp),
- "
lock xadd [rsp - 8], rdx
",
- "
lock bts qword ptr [rsp - 8], 1
"
Trong mọi trường hợp thời gian đo được khoảng 310 chu kỳ, lỗi là khoảng +/- 8 chu kỳ
Đây là giá trị cho việc thực hiện lặp lại trên cùng một bộ nhớ (được lưu trong bộ nhớ cache). Với một bộ nhớ cache bổ sung bỏ lỡ, thời gian cao hơn đáng kể. Ngoài ra điều này được thực hiện chỉ với một trong 2 lõi hoạt động, do đó bộ nhớ cache đã được sở hữu độc quyền và không có bộ nhớ cache synchonisation được yêu cầu.
Để đánh giá chi phí của lệnh bị khóa trên bộ nhớ cache, tôi đã thêm lệnh wbinvld
trước lệnh bị khóa và đặt wbinvld
cộng với số add [rsp - 8], rax
vào vòng so sánh. Trong cả hai trường hợp, chi phí là khoảng 80.000 chu kỳ mỗi cặp lệnh! Trong trường hợp của khóa bts sự khác biệt thời gian là khoảng 180 chu kỳ mỗi hướng dẫn.
Lưu ý rằng đây là thông lượng đối ứng, nhưng do các hoạt động bị khóa đang hoạt động nối tiếp, có thể không có sự khác biệt về độ trễ.
Kết luận: hoạt động bị khóa quá nặng nhưng thiếu bộ nhớ cache có thể nặng hơn nhiều. Ngoài ra: thao tác bị khóa không gây ra lỗi bộ nhớ cache. Nó chỉ có thể gây ra lưu lượng truy cập đồng bộ hóa bộ nhớ cache, khi một bộ nhớ đệm không thuộc sở hữu độc quyền.
Để khởi động máy, tôi đã sử dụng phiên bản x64 của FreeLdr từ dự án ReactOS. Đây là mã nguồn asm:
#define LOOP_COUNT 1000
#define UNROLLED_COUNT 100
PUBLIC ProfileDummy
ProfileDummy:
cli
// Get current TSC value into r8
rdtsc
mov r8, rdx
shl r8, 32
or r8, rax
mov rcx, LOOP_COUNT
jmp looper1
.align 16
looper1:
REPEAT UNROLLED_COUNT
// nothing, or add something to compare against
ENDR
dec rcx
jnz looper1
// Put new TSC minus old TSC into rax
rdtsc
shl rdx, 32
or rax, rdx
sub rax, r8
ret
PUBLIC ProfileFunction
ProfileFunction:
cli
rdtsc
mov r8, rdx
shl r8, 32
or r8, rax
mov rcx, LOOP_COUNT
jmp looper2
.align 16
looper2:
REPEAT UNROLLED_COUNT
// Put here the code you want to profile
// make sure it doesn't mess up non-volatiles or r8
lock bts qword ptr [rsp - 8], 1
ENDR
dec rcx
jnz looper2
rdtsc
shl rdx, 32
or rax, rdx
sub rax, r8
ret
hoạt động nguyên tử nào? –
@ Jason S, Any. Sự khác biệt giữa cas và inc/dec nguyên tử là không đáng kể. – osgx
Các hoạt động nguyên tử trên x86 trở nên chậm hơn khi có nhiều tranh chấp được đặt vào địa chỉ bộ nhớ. Tôi tin rằng nói chung họ đang xung quanh một đơn đặt hàng của cường độ chậm hơn so với hoạt động không bị khóa, nhưng rõ ràng điều này sẽ thay đổi tùy thuộc vào hoạt động, tranh chấp và các rào cản bộ nhớ được sử dụng. –