Tôi muốn biết điều tương tự, vì vậy tôi đã đo nó. Trên hộp của tôi (AMD FX (tm) -8150 Bộ xử lý tám lõi tốc độ 3.612361 GHz), khóa và mở khóa một mutex đã được mở khóa trong dòng bộ nhớ cache của riêng nó và đã được lưu vào bộ nhớ cache, mất 47 đồng hồ (13 ns).
Do đồng bộ hóa giữa hai lõi (tôi đã sử dụng CPU # 0 và # 1), tôi chỉ có thể gọi một cặp khóa / mở khóa một lần trong 102 ns trên hai luồng, cứ sau 51 giây, người ta có thể kết luận rằng phải mất khoảng 38 ns ns để phục hồi sau khi một luồng thực hiện mở khóa trước khi luồng tiếp theo có thể khóa lại.
Chương trình mà tôi đã sử dụng để điều tra điều này có thể được tìm thấy ở đây:
https://github.com/CarloWood/ai-statefultask-testsuite/blob/b69b112e2e91d35b56a39f41809d3e3de2f9e4b8/src/
Lưu ý rằng nó có một vài giá trị được mã hóa cụ thể cho hộp của tôi (xrange, yrange và rdtsc trên đầu), vì vậy bạn có thể phải thử nghiệm với nó trước khi nó hoạt động cho bạn.
Biểu đồ mà nó tạo ra ở trạng thái đó là:
Điều này cho thấy kết quả của điểm chuẩn chạy trên mã sau đây:
uint64_t do_Ndec(int thread, int loop_count)
{
uint64_t start;
uint64_t end;
int __d0;
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (start) : : "%rdx");
mutex.lock();
mutex.unlock();
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (end) : : "%rdx");
asm volatile ("\n1:\n\tdecl %%ecx\n\tjnz 1b" : "=c" (__d0) : "c" (loop_count - thread) : "cc");
return end - start;
}
Hai cuộc gọi ndtsc đo số lượng đồng hồ cần thiết để khóa và mở khóa 'mutex' (với tổng số 39 đồng hồ cho các cuộc gọi của ndtsc trên hộp của tôi). Asm thứ ba là một vòng lặp trì hoãn. Kích thước của vòng lặp trễ là 1 đếm nhỏ hơn cho luồng 1 so với luồng 0, do đó, luồng 1 nhanh hơn một chút.
Hàm trên được gọi trong một vòng lặp chặt chẽ có kích thước 100.000. Mặc dù chức năng này nhanh hơn một chút đối với luồng 1, cả hai vòng đều đồng bộ hóa do cuộc gọi đến mutex. Điều này có thể nhìn thấy trong biểu đồ từ thực tế là số lượng đồng hồ được đo cho cặp khóa / mở khóa lớn hơn một chút cho luồng 1, để tính độ trễ ngắn hơn trong vòng lặp bên dưới nó.
Trong biểu đồ trên, điểm dưới cùng bên phải là một phép đo có độ trễ loop_count là 150, và sau đó theo các điểm ở phía dưới, về phía bên trái, loop_count được giảm đi mỗi lần đo. Khi nó trở thành 77, hàm được gọi cứ 102 ns trong cả hai luồng. Nếu loop_count sau đó bị giảm hơn nữa thì không thể đồng bộ hóa các luồng và mutex bắt đầu thực sự bị khóa hầu hết thời gian, dẫn đến số lượng đồng hồ tăng lên để thực hiện khóa / mở khóa. Ngoài ra thời gian trung bình của cuộc gọi chức năng tăng vì điều này; Vì vậy, các điểm cốt truyện bây giờ đi lên và về phía bên phải một lần nữa.
Từ điều này, chúng tôi có thể kết luận rằng việc khóa và mở khóa một mutex cứ sau 50 ns không phải là vấn đề trong hộp của tôi.
Tất cả trong tất cả kết luận của tôi là câu trả lời cho câu hỏi của OP là thêm nhiều mutexes sẽ tốt hơn miễn là điều đó dẫn đến sự tranh chấp ít hơn.
Cố gắng khóa mutexes càng ngắn càng tốt. Lý do duy nhất để đặt chúng - bên ngoài một vòng lặp sẽ là nếu vòng lặp đó lặp lại nhanh hơn một lần trong mỗi 100 ns (hay đúng hơn là số luồng muốn chạy vòng lặp đó cùng lúc 50 lần) hoặc khi 13 lần kích thước vòng lặp chậm hơn so với độ trễ bạn nhận được bằng sự tranh chấp.
EDIT: Bây giờ tôi đã hiểu biết nhiều hơn về chủ đề này và bắt đầu nghi ngờ về kết luận mà tôi đã trình bày ở đây. Trước hết, CPU 0 và 1 hóa ra là siêu luồng; mặc dù AMD tuyên bố có 8 lõi thực sự, nhưng chắc chắn có điều gì đó rất đáng nghi vì độ trễ giữa hai lõi khác lớn hơn nhiều (nghĩa là 0 và 1 tạo thành một cặp, cũng như 2 và 3, 4 và 5, và 6 và 7 ). Thứ hai, std :: mutex được triển khai theo cách nó quay khóa một chút trước khi thực sự thực hiện các cuộc gọi hệ thống khi không nhận được khóa ngay lập tức trên một mutex (điều này chắc chắn sẽ rất chậm). Vì vậy, những gì tôi đã đo được ở đây là sự bão hòa lý tưởng tuyệt đối nhất và trong thực tế việc khóa và mở khóa có thể mất nhiều thời gian hơn cho mỗi lần khóa / mở khóa.
Tóm lại, một mutex được thực hiện với nguyên tử. Để đồng bộ hóa các nguyên tử giữa các lõi, một bus nội bộ phải được khóa để đóng băng dòng bộ đệm tương ứng trong vài trăm chu kỳ xung nhịp. Trong trường hợp không thể lấy được khóa, một cuộc gọi hệ thống phải được thực hiện để đưa luồng vào chế độ ngủ; điều đó rõ ràng là rất chậm (các cuộc gọi hệ thống theo thứ tự 10 mircos giây). Thông thường đó không thực sự là vấn đề vì dù sao thì luồng đó cũng phải ngủ-- nhưng nó có thể là một vấn đề với sự tranh chấp cao trong đó một luồng không thể có được khóa trong thời gian mà nó thường quay và hệ thống cũng gọi, nhưng CÓ THỂ mất khóa ngay sau đó. Ví dụ: nếu một số luồng khóa và mở khóa một mutex trong một vòng lặp chặt chẽ và mỗi luồng giữ khóa trong 1 micro giây hoặc lâu hơn, sau đó họ có thể bị chậm lại rất nhiều bởi thực tế là họ liên tục bị ngủ và thức dậy một lần nữa. Ngoài ra, một khi một luồng ngủ và một luồng khác phải đánh thức nó, luồng đó phải thực hiện một cuộc gọi hệ thống và bị trì hoãn ~ 10 micro giây; do đó, độ trễ này xảy ra trong khi mở khóa một mutex khi một luồng khác đang chờ mutex đó trong kernel (sau khi quay quá lâu).