chi phí vận hành nguyên tử


90

Chi phí của hoạt động nguyên tử (bất kỳ so sánh và hoán đổi hoặc thêm / giảm nguyên tử) là gì? Nó tiêu thụ bao nhiêu chu kỳ? Nó sẽ tạm dừng các bộ xử lý khác trên SMP hoặc NUMA hay nó sẽ chặn truy cập bộ nhớ? Nó sẽ sắp xếp lại bộ đệm trong CPU không theo thứ tự?

Những tác động nào sẽ có trên bộ nhớ cache?

Tôi quan tâm đến các CPU hiện đại, phổ biến: x86, x86_64, PowerPC, SPARC, Itanium.


@Jason S, Bất kỳ. Sự khác biệt giữa cas và inc / dec nguyên tử là không đáng kể.
osgx

2
Các hoạt động nguyên tử trên x86 trở nên chậm hơn khi có nhiều tranh cãi hơn về địa chỉ bộ nhớ. Tôi tin rằng nói chung, chú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ẽ khác nhau tùy thuộc vào hoạt động, tranh chấp và rào cản bộ nhớ được sử dụng.
Stephen Nutt

hmmm. viết có vẻ là nguyên tử trên x86. 'Hiểu về hạt nhân Linux' -> spin_unlock
osgx

Một 32 bit ghi là nguyên tử trong Java, tức là nó có thể là nguyên tử (nhưng không có ngữ nghĩa rào cản bộ nhớ, vì vậy điều này thường không đủ cho con trỏ). Thêm 1 thường không phải là nguyên tử, trừ khi bạn thêm tiền tố LOCK. Về nhân Linux, không cần phải xem spin_unlock. Hãy xem, trong các bản phát hành hiện tại, Arch / x86 / include / asm / atom_32.h (nó từng là include / asm-i386 / atom.h).
Blaisorblade

@Blaisorblade, JAva không có ở đây. Chi phí của các hoạt động bị KHÓA là gì?
osgx

Câu trả lời:


59

Tôi đã tìm kiếm dữ liệu thực tế trong những ngày qua và không tìm thấy gì. Tuy nhiên, tôi đã thực hiện một số nghiên cứu, so sánh chi phí của các hoạt động nguyên tử với chi phí của các bộ nhớ cache.

Chi phí của tiền tố x86 LOCK, (bao gồm cả lock cmpxchgCAS nguyên tử), trước PentiumPro (như được mô tả trong tài liệu), là quyền truy cập bộ nhớ (như lỗi bộ nhớ đệm), + dừng hoạt động bộ nhớ của bộ xử lý khác, + bất kỳ tranh chấp nào với bộ xử lý khác cố gắng KHÓA xe buýt. Tuy nhiên, kể từ PentiumPro, đối với bộ nhớ cache có thể ghi lại thông thường (tất cả bộ nhớ mà ứng dụng xử lý, trừ khi bạn nói chuyện trực tiếp với phần cứng), thay vì chặn tất cả các hoạt động của bộ nhớ, chỉ dòng bộ nhớ cache liên quan bị chặn (dựa trên liên kết trong câu trả lời của @ osgx ) .

tức là phần lõi sẽ trì hoãn việc trả lời các yêu cầu chia sẻ MESI và RFO cho đường dây cho đến sau khi phần lưu trữ của lockhoạt động ed thực tế . Đây được gọi là "khóa bộ đệm" và chỉ ảnh hưởng đến một dòng bộ đệm. Các lõi khác có thể đang tải / lưu trữ hoặc thậm chí CASing các dòng khác cùng một lúc.


Trên thực tế, trường hợp CAS có thể phức tạp hơn, như được giải thích trên trang này , không có thời gian nhưng được một kỹ sư đáng tin cậy mô tả sâu sắc. (Ít nhất là đối với trường hợp sử dụng thông thường, nơi bạn thực hiện tải thuần túy trước CAS thực tế.)

Trước khi đi vào quá nhiều chi tiết, tôi sẽ nói rằng thao tác LOCKed tốn một lần bỏ lỡ bộ nhớ đệm + khả năng xảy ra tranh chấp với bộ xử lý khác trên cùng một dòng bộ nhớ cache, trong khi CAS + tải trước (hầu như luôn luôn bắt buộc ngoại trừ trên mutexes, nơi bạn luôn CAS 0 và 1) có thể làm hỏng hai bộ nhớ cache.

Anh ấy giải thích rằng một tải + CAS trên một vị trí thực sự có thể gây ra hai lần bỏ lỡ bộ nhớ cache, như Load-Linked / Store-Conditional (xem ở đó để biết phần sau). Lời giải thích của ông dựa trên kiến ​​thức về giao thức kết hợp bộ nhớ cache MESI . Nó sử dụng 4 trạng thái cho dòng cache: M (odified), E (xclusive), S (hared), I (nvalid) (và do đó nó được gọi là MESI), được giải thích bên dưới khi cần thiết. Kịch bản được giải thích như sau:

  • LOAD gây ra lỗi bộ nhớ cache - dòng bộ đệm liên quan được tải từ bộ nhớ ở trạng thái Chia sẻ (tức là các bộ xử lý khác vẫn được phép giữ dòng bộ đệm đó trong bộ nhớ; không cho phép thay đổi trong trạng thái này). Nếu vị trí nằm trong bộ nhớ, bỏ qua bộ nhớ cache này sẽ bị bỏ qua. Chi phí có thể xảy ra: 1 lần bỏ lỡ bộ nhớ cache. (bỏ qua nếu dòng bộ đệm ở trạng thái Chia sẻ, Độc quyền hoặc Sửa đổi, tức là dữ liệu nằm trong bộ đệm L1 của CPU này).
  • chương trình tính toán các giá trị mới để lưu trữ,
  • và nó chạy một lệnh CAS nguyên tử.
    • Nó phải tránh sửa đổi đồng thời, vì vậy nó phải xóa các bản sao của dòng bộ đệm khỏi bộ đệm của các CPU khác, để chuyển dòng bộ đệm sang trạng thái Độc quyền. Chi phí có thể xảy ra: 1 lần bỏ lỡ bộ nhớ cache. Điều này không cần thiết nếu nó đã được sở hữu độc quyền, tức là ở trạng thái Độc quyền hoặc Đã sửa đổi. Trong cả hai trạng thái, không có CPU nào khác giữ dòng bộ nhớ cache, nhưng ở trạng thái Độc quyền, nó vẫn chưa được sửa đổi (chưa).
    • Sau giao tiếp này, biến được sửa đổi trong bộ nhớ cache cục bộ của CPU của chúng tôi, tại thời điểm đó, nó được hiển thị trên toàn cầu đối với tất cả các CPU khác (vì bộ nhớ cache của chúng đồng nhất với bộ nhớ cache của chúng ta). Cuối cùng nó sẽ được ghi vào bộ nhớ chính theo các thuật toán thông thường.
    • Các bộ xử lý khác đang cố gắng đọc hoặc sửa đổi biến đó trước tiên sẽ phải lấy dòng bộ nhớ đệm đó ở chế độ Chia sẻ hoặc Độc quyền và để làm như vậy, bộ xử lý này sẽ liên hệ với bộ xử lý này và nhận phiên bản cập nhật của dòng bộ nhớ đệm. Thay vào đó, một hoạt động LOCKed chỉ có thể khiến bộ nhớ cache bị bỏ lỡ (vì dòng bộ nhớ cache sẽ được yêu cầu trực tiếp ở trạng thái Độc quyền).

Trong mọi trường hợp, yêu cầu dòng bộ nhớ cache có thể bị đình trệ bởi các bộ xử lý khác đã sửa đổi dữ liệu.


tại sao chaning của trạng thái trên các cp khác lại tốn kém như 1 bộ nhớ cache?
osgx

1
Bởi vì nó giao tiếp bên ngoài CPU, và do đó chậm hơn so với truy cập bộ nhớ cache. Mặc dù dù sao một lần bỏ lỡ bộ nhớ cache cũng phải chuyển từ các CPU khác. Trên thực tế, có thể xảy ra trường hợp nói chuyện với CPU khác nhanh hơn nói chuyện với bộ nhớ, nếu kết nối trực tiếp được sử dụng, như AMD Hypertransport (từ rất lâu trước đây) hoặc Intel QuickPath Interconnect của Intel, trên bộ vi xử lý Xeon mới nhất dựa trên Nehalem. Nếu không, giao tiếp với các CPU khác diễn ra trên cùng một FSB như FSB cho bộ nhớ. Tìm kiếm HyperTransport và Front Side Bus trên Wikipedia để biết thêm thông tin.
Blaisorblade

Chà, chưa bao giờ nghĩ rằng nó lại đắt đến vậy - một lần bỏ lỡ bộ nhớ cache có thể là vài nghìn chu kỳ.
Lothar

2
Có thật không? Hình mà tôi quen là: một trăm chu kỳ cho các lần bỏ lỡ bộ nhớ cache và hàng ngàn chu kỳ cho các chuyển mạch ngữ cảnh / đặc quyền (bao gồm cả cuộc gọi tổng hợp).
Blaisorblade

1
Bộ nhớ cache bỏ lỡ không phải là vài nghìn chu kỳ! Khoảng 100ns của nó, thường là 300-350 chu kỳ CPU ....
user997112

37

Tôi đã thực hiện một số cấu hình với thiết lập 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 một vòng lặp, 100 lần lặp được bỏ cuộn và 1.000 chu kỳ vòng lặp. Nội dung vòng lặp đã được căn chỉnh thành 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ả không có lệnh nào được thực hiện (đo 2 chu kỳ cho mỗi lần lặp vòng lặp và 14 chu kỳ cho phần còn lại) và kết quả được trừ từ kết quả của thời gian lập hồ sơ lệnh.

Các hướng dẫn sau được đo:

  • "lock cmpxchg [rsp - 8], rdx " (cả đối sánh so sánh và không khớp),
  • "lock xadd [rsp - 8], rdx ",
  • "lock bts qword ptr [rsp - 8], 1 "

Trong tất cả các trường hợp, thời gian đo được là khoảng 310 chu kỳ, sai số là khoảng +/- 8 chu kỳ

Đây là giá trị để thực hiện lặp lại trên cùng một bộ nhớ (được lưu trong bộ nhớ đệm). Với việc bỏ lỡ bộ nhớ cache bổ sung, thời gian sẽ cao hơn đáng kể. Ngoài ra, điều này được thực hiện với chỉ một trong 2 lõi hoạt động, vì vậy bộ nhớ cache được sở hữu độc quyền và không cần đồng bộ hóa bộ nhớ cache.

Để đánh giá chi phí của một lệnh bị khóa khi bỏ lỡ bộ nhớ cache, tôi đã thêm một wbinvldlệnh trước lệnh bị khóa và đặt dấu wbinvldcộngadd [rsp - 8], rax vào vòng lặp so sánh. Trong cả hai trường hợp, chi phí là khoảng 80.000 chu kỳ cho mỗi cặp lệnh! Trong trường hợp khóa bts, chênh lệch thời gian là khoảng 180 chu kỳ cho mỗi lệnh.

Lưu ý rằng đây là thông lượng tương hỗ, nhưng vì các hoạt động bị khóa là các hoạt động tuần tự hóa, có thể không có sự khác biệt đối với độ trễ.

Kết luận: một hoạt động bị khóa là nặng, nhưng bỏ lỡ bộ nhớ cache có thể nặng hơn nhiều. Ngoài ra: một hoạt động bị khóa không gây ra bỏ lỡ bộ nhớ cache. Nó chỉ có thể gây ra lưu lượng đồng bộ hóa bộ nhớ cache, khi một dòng bộ nhớ cache không đượ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

Cảm ơn! Bạn có thể xuất bản mã thử nghiệm của mình hoặc tự kiểm tra Core2 / Core i3 / i5 / i7 được không? Tất cả các lõi đã được khởi tạo trong thiết lập thử nghiệm của bạn chưa?
osgx

Tôi đã thêm mã nguồn. Chỉ một lõi được khởi tạo. Rất thích xem kết quả từ các máy khác.
Timo

CLFLUSH nên là một cách nhẹ hơn nhiều để xóa một dòng bộ nhớ cache so với WBINVD của toàn bộ bộ nhớ cache. WBINVD cũng sẽ xóa bộ nhớ đệm hướng dẫn, dẫn đến việc bỏ lỡ thêm bộ nhớ cache.
Peter Cordes

Có lẽ thú vị khi kiểm tra trường hợp dòng bộ nhớ cache bị nóng ở trạng thái Chia sẻ. Bạn có thể biến điều đó thành hiện thực bằng cách yêu cầu một luồng khác đọc nó với một tải thuần túy.
Peter Cordes

4

Trên SMP dựa trên bus, tiền tố nguyên tử LOCKkhẳng định (bật) tín hiệu dây bus LOCK#. Nó sẽ cấm CPU / thiết bị khác trên xe buýt sử dụng nó.

Sách Ppro & P2 http://books.google.com.vn/books?id=3gDmyIYvFH4C&pg=PA245&dq=lock+instruction+pentium&lr=&ei=_E61S5ehLI78zQSzrqwI&cd=1#v=onepage&q=lock%20instruction%20pentium&f=false trang 244-246

Các hướng dẫn đã khóa đang tuần tự hóa, đồng bộ hóa hoạt động .... / about Out-of-order / lock RMW / read-mod-write = own / lệnh đảm bảo rằng bộ xử lý sẽ thực hiện tất cả các lệnh trước lệnh bị khóa trước khi thực thi nó. / about chưa được xóa ghi / nó buộc tất cả các lần ghi đã đăng trong bộ xử lý được chuyển sang bộ nhớ ngoài trước khi thực hiện lệnh tiếp theo.

/ about SMP / semaphore đang ở trong bộ nhớ cache ở trạng thái S ... phát hành một giao dịch đọc và vô hiệu hóa trong 0 byte ngày (đây là một lần hủy / các bản sao được chia sẻ của dòng bộ đệm trong các CPU liền kề /)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.