Aynı şeyi bilmek istedim, bu yüzden ölçtüm. Kutumda (AMD FX (tm) -8150 3.612361 GHz'de Sekiz Çekirdekli İşlemci), kendi önbellek satırında bulunan ve zaten önbelleğe alınmış olan kilidi açılmış bir muteksi kilitlemek ve kilidini açmak 47 saat (13 ns) alır.
İki çekirdek arasındaki senkronizasyon nedeniyle (CPU # 0 ve # 1'i kullandım), iki iş parçacığında 102 ns'de bir kez bir kilitleme / kilit açma çiftini çağırabilirim, bu yüzden her 51 ns'de bir, kabaca 38 aldığı sonucuna varabilir ns bir iş parçacığı bir sonraki iş parçacığı yeniden kilitlemek önce bir kilit açma yaptıktan sonra kurtarmak için.
Bunu araştırmak için kullandığım program burada bulunabilir:
https://github.com/CarloWood/ai-statefultask-testsuite/blob/b69b112e2e91d35b56a39f41809d3e3de2f9e4b8/src/mutex_test.cxx
Kutum için belirli birkaç sabit kodlu değere sahip olduğunu unutmayın (xrange, yrange ve rdtsc ek yükü), bu yüzden muhtemelen sizin için çalışmadan önce bunu denemeniz gerekir.
Bu durumda ürettiği grafik:
Bu, karşılaştırma kodunun aşağıdaki kodda çalışmasının sonucunu gösterir:
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;
}
İki rdtsc çağrısı, mutex'i kilitlemek ve kilidini açmak için gereken saat sayısını ölçer (kutumdaki rdtsc aramaları için 39 saat yükü ile). Üçüncü asm bir gecikme döngüsüdür. Gecikme döngüsünün boyutu, iplik 1 için iplik 0 için olduğundan 1 sayı daha küçüktür, bu nedenle iplik 1 biraz daha hızlıdır.
Yukarıdaki işlev, 100.000 büyüklüğünde sıkı bir döngüde çağrılır. İşlevin iş parçacığı 1 için biraz daha hızlı olmasına rağmen, muteks çağrısı nedeniyle her iki döngü de eşitlenir. Bu, grafikte, kilitleme / kilit açma çifti için ölçülen saat sayısının, altındaki döngüdeki daha kısa gecikmeyi hesaba katmak için iplik 1 için biraz daha fazla olması gerçeğinden görülebilir.
Yukarıdaki grafikte sağ alt nokta 150 gecikme loop_count değerine sahip bir ölçümdür ve daha sonra alttaki noktaları sola doğru takip ederek loop_count her ölçümde bir azaltılır. 77 olduğunda fonksiyon her iki iş parçacığında da 102 ns olarak adlandırılır. Daha sonra loop_count daha da azaltılırsa, dişleri senkronize etmek artık mümkün değildir ve muteks çoğu zaman gerçekten kilitlenmeye başlar, bu da kilitleme / kilidini açmak için gereken saatlerin artmasına neden olur. Ayrıca fonksiyon çağrısının ortalama süresi bu nedenle artar; böylece çizim noktaları tekrar yukarı ve sağa doğru gidiyor.
Buradan, her 50 ns'de bir muteksi kilitlemenin ve kilidini açmanın kutumda bir sorun olmadığı sonucuna varabiliriz.
Sonuç olarak, OP sorusunun cevabı, daha az çekişme ile sonuçlandığı sürece daha fazla muteks eklemenin daha iyi olduğudur.
Muteksleri olabildiğince kısa tutmaya çalışın. Onları bir döngü dışına koymanın tek nedeni, bu döngü 100 ns'den bir kez (veya daha doğrusu, aynı döngüyü 50 ns kez çalıştırmak isteyen iş parçacığı sayısından) veya 13 ns'den daha hızlı döngüye girerse döngü boyutu, çekişme yoluyla aldığınız gecikmeden daha fazla gecikmedir.
DÜZENLEME: Şimdi konuyla ilgili çok daha bilgili oldum ve burada sunduğum sonuçtan şüphe etmeye başladım. Her şeyden önce, CPU 0 ve 1 hiper iş parçacıklıdır; AMD'nin 8 gerçek çekirdeğe sahip olduğunu iddia etmesine rağmen, kesinlikle çok balıklı bir şey var çünkü diğer iki çekirdek arasındaki gecikmeler çok daha büyük (yani 0 ve 1, 2 ve 3, 4 ve 5 ve 6 ve 7 gibi bir çift oluşturuyor) ). İkincisi, std :: mutex, bir muteks üzerindeki kilidi hemen alamadığında sistem çağrıları yapmadan önce kilitleri biraz döndürecek şekilde uygulanır (şüphesiz son derece yavaş olacaktır). Yani burada ölçtüğüm kesinlikle en ideal durum ve pratikte kilitleme ve kilit açma kilit / kilit açma başına büyük ölçüde daha fazla zaman alabilir.
Sonuç olarak, atomlarla bir muteks uygulanır. Çekirdekler arasındaki atomları senkronize etmek için, birkaç önbellek hattını birkaç yüz saat döngü boyunca donatan bir dahili veri yolu kilitlenmelidir. Bir kilidin elde edilememesi durumunda, ipliği uyku moduna geçirmek için bir sistem çağrısı yapılmalıdır; bu son derece yavaştır (sistem çağrıları 10 mirisaniyedir). Normalde bu gerçekten bir sorun değildir, çünkü iş parçacığının yine de uyuması gerekir - ancak bir iş parçacığının normalde döndüğü süre boyunca kilidi alamadığı ve sistem çağrısı yaptığı yüksek çekişme ile ilgili bir sorun olabilir, ancak CAN kısa süre sonra kilidi al. Örneğin, birkaç iş parçacığı bir muteksi sıkı bir döngüde kilitleyip kilitlerse ve her biri kilidi 1 mikrosaniye kadar tutarsa, o zaman sürekli uyumaları ve tekrar uyandırılmaları nedeniyle çok yavaşlayabilirler. Ayrıca, bir iş parçacığı uyuduğunda ve başka bir iş parçacığının onu uyandırması gerektiğinde, bu iş parçacığı bir sistem çağrısı yapmak zorundadır ve ~ 10 mikrosaniye geciktirilir; dolayısıyla bu gecikme, çekirdekteki başka bir iş parçacığını beklerken bir muteksin kilidini açarken olur (eğirme çok uzun sürdükten sonra).