Kilit ifadesi ne kadar pahalı?


111

Çoklu iş parçacığı ve paralel işleme ile deneyler yapıyordum ve işlem hızının bazı temel sayım ve istatistik analizlerini yapmak için bir sayaca ihtiyacım vardı. Sınıfımın eşzamanlı kullanımıyla ilgili sorunları önlemek için sınıfımdaki özel bir değişken üzerinde bir kilit ifadesi kullandım:

private object mutex = new object();

public void Count(int amount)
{
 lock(mutex)
 {
  done += amount;
 }
}

Ama merak ediyordum ... bir değişkeni kilitlemek ne kadar pahalı? Performans üzerindeki olumsuz etkiler nelerdir?


10
Değişkeni kilitlemek o kadar da pahalı değil; kaçınmak istediğiniz kilitli bir değişkeni beklemektir.
Gabe

53
başka bir yarış durumunu izlemek için saatler harcamaktan çok daha ucuz ;-)
BrokenGlass

2
Pekala ... eğer bir kilit pahalıysa, daha az kilit gerektirecek şekilde programlamayı değiştirerek onlardan kaçınmak isteyebilirsiniz. Bir çeşit senkronizasyon uygulayabilirim.
Kees C.Bakker

1
Kilit bloklarımdan çok sayıda kod taşıyarak (şu anda @Gabe'nin yorumunu okuduktan sonra) performansta çarpıcı bir gelişme yaşadım. Alt satır: Şu andan itibaren bir kilit bloğu içinde sadece değişken erişimi (genellikle bir satır) bırakacağım, bir çeşit "tam zamanında kilitleme". Mantıklı geliyor?
heltonbiker

2
@heltonbiker Elbette mantıklı. Aynı zamanda mimari prensip olmalı, mümkün olduğunca kısa, basit ve hızlı kilitler yapmanız gerekiyor. Yalnızca senkronize edilmesi gereken gerçekten gerekli veriler. Sunucu kutularında, kilidin hibrit doğasını da göz önünde bulundurmalısınız. Kodunuz için kritik olmasa bile ihtilaf, kilidin karma yapısı sayesinde, kilit başka biri tarafından tutulursa her erişim sırasında çekirdeklerin dönmesine neden olur. İş parçacığınız askıya alınmadan önce bir süre sunucudaki diğer hizmetlerden bazı cpu kaynaklarını etkili bir şekilde tüketiyorsunuz.
ipavlu

Yanıtlar:


86

İşte maliyete giren bir makale . Kısa cevap 50ns'dir.


39
Kısa, daha iyi cevap: 50ns + diğer iş parçacığı kilidi tutuyorsa bekleme süresi.
Herman

4
Ne kadar çok iş parçacığı kilide girip çıkarsa, o kadar pahalı olur. Maliyet, iş parçacığı sayısıyla katlanarak
artıyor

16
Bazı bağlamlar: 3Ghz x86'da iki sayıyı bölmek yaklaşık 10ns sürer (talimatı getirmek / kodunu çözmek için gereken süre dahil değildir) ; ve tek bir değişkeni (önbelleğe alınmamış) bir kayıt listesine yüklemek yaklaşık 40ns sürer. Yani 50ns delice, göz kamaştırıcı derecede hızlı - lockbir değişken kullanmanın maliyeti hakkında endişelenmekten daha fazlasını kullanmanın maliyeti konusunda endişelenmemelisiniz.
BlueRaja - Dany Pflughoeft

3
Ayrıca, bu soru sorulduğunda o makale eskiydi.
Otis

3
Gerçekten harika bir metrik, "neredeyse hiç maliyet yok", yanlıştan bahsetmiyorum bile. Siz çocuklar bunun kısa ve hızlı olduğunu ve SADECE herhangi bir çekişme yoksa tek bir konu olduğunu dikkate almıyorsunuz. BÖYLE BİR DURUMDA KİLİTLEMENİZ GEREKMEZ. İkinci konu, kilit kilit değil, hibrit kilittir, CLR içinde kilidin atomik işlemlere dayalı olarak kimsenin elinde olmadığını tespit eder ve bu durumda işletim sistemi çekirdeğine, yani bunlarla ölçülemeyen farklı halka çağrılarına engel olur. testleri. 25ns - 50ns olarak ölçülen şey aslında kilit alınmadıysa uygulama seviyesinde kilitli talimat kodudur
ipavlu

50

Teknik cevap, bunun ölçülmesinin imkansız olduğu, büyük ölçüde CPU belleğinin geri yazma tamponlarının durumuna ve ön belleğin topladığı verinin atılması ve yeniden okunması gerektiğine bağlı olmasıdır. Her ikisi de çok deterministik değil. Büyük hayal kırıklıklarını önleyen, zarfın arkası yaklaşımı olarak 150 CPU döngüsü kullanıyorum.

Pratik cevabım olmasıdır waaaay size bir kilit atlayabilirsiniz dendiğinde sizin hata ayıklama kodu yakacağız süreyi daha ucuz.

Zor bir sayı elde etmek için ölçmeniz gerekecek. Visual Studio, uzantı olarak kullanılabilen kaygan bir eşzamanlılık analizcisine sahiptir .


1
Aslında hayır, ölçülebilir ve ölçülebilir. Bu kilitleri kodun etrafına yazmak kadar kolay değil, sonra hepsinin sadece 50ns olduğunu belirtmek, kilide tek iş parçacıklı erişimde ölçülen bir efsane.
ipavlu

8
"bir kilidi atlayabileceğinizi düşünüyorum" ... Sanırım bu soruyu okuduklarında pek çok insanın bulunduğu yer burası ...
Snoop

30

Daha fazla okuma:

Genel senkronizasyon ilkelleri ile ilgilenen ve farklı senaryolara ve iş parçacığı sayısına bağlı olarak Monitör, C # kilit ifadesi davranışı, özellikleri ve maliyetlerini araştıran birkaç makalemi sunmak istiyorum. Birden çok senaryoda ne kadar çalışmanın gerçekleştirilebileceğini anlamak için özellikle CPU israfı ve işlem süreleri ile ilgilenir:

https://www.codeproject.com/Articles/1236238/Unified-Concurrency-I-Introduction https://www.codeproject.com/Articles/1237518/Unified-Concurrency-II-benchmarking-methodologies https: // www. codeproject.com/Articles/1242156/Unified-Concurrency-III-cross-benchmarking

Orijinal cevap:

Ah hayatım!

Görünüşe göre burada CEVAP olarak işaretlenen doğru cevap, doğası gereği yanlış! Saygılarımla, cevabın yazarından bağlantılı makaleyi sonuna kadar okumasını rica ediyorum. makale

2003 tarihli makalenin yazarı yalnızca Çift Çekirdekli makinede ölçüm yapıyordu ve ilk ölçüm durumunda, kilitlemeyi yalnızca tek bir iş parçacığı ile ölçtü ve sonuç, kilit erişimi başına yaklaşık 50ns oldu.

Eşzamanlı ortamda bir kilit hakkında hiçbir şey söylemiyor. Bu yüzden makaleyi okumaya devam etmeliyiz ve ikinci yarıda yazar kilitleme senaryosunu iki ve üç iş parçacığı ile ölçüyordu, bu da günümüz işlemcilerinin eşzamanlılık düzeylerine yaklaşıyor.

Bu yüzden yazar, Çift Çekirdekli iki iş parçacığı ile kilitlerin 120ns'ye mal olduğunu ve 3 iş parçacığı ile 180ns'ye gittiğini söylüyor. Dolayısıyla, kilide aynı anda erişen iş parçacığı sayısına açıkça bağlı görünüyor.

Yani basittir, kilidin işe yaramaz hale geldiği tek bir iş parçacığı olmadığı sürece 50 ns değildir.

Dikkate alınması gereken bir diğer konu da, ortalama süre olarak ölçülmesidir !

Yinelemelerin süresi ölçülürse, 1 ms ile 20 ms arasında bile zamanlar olurdu, çünkü çoğunluk hızlıydı, ancak birkaç iş parçacığı işlemcilerin zamanını bekliyor olacak ve hatta milisaniyelik uzun gecikmelere neden olacak.

Bu, yüksek verim, düşük gecikme süresi gerektiren her tür uygulama için kötü bir haber.

Ve dikkate alınması gereken son konu, kilidin içinde daha yavaş işlemlerin olabileceğidir ve çoğu zaman durum böyledir. Kod bloğu kilidin içinde ne kadar uzun süre çalıştırılırsa, çekişme o kadar yüksek olur ve gecikmeler gökyüzüne yükselir.

Lütfen, 2003'ten on yıldan fazla bir süre geçtiğini, yani tamamen eşzamanlı olarak çalışacak şekilde özel olarak tasarlanmış birkaç nesil işlemci olduğunu ve kilitlemenin performanslarına önemli ölçüde zarar verdiğini göz önünde bulundurun.


1
Açıklığa kavuşturmak gerekirse, makale, uygulamadaki iş parçacığı sayısı ile kilit performansının düştüğünü söylemiyor; performans, kilit üzerinde rekabet eden iş parçacığı sayısıyla düşer. (Yukarıdaki cevapta ima edilmiştir, ancak açıkça belirtilmemiştir.)
Bektaşi üzümü

Şunu kastettiğinizi varsayıyorum: "Dolayısıyla, eşzamanlı olarak erişilen iş parçacığı sayısına açıkça bağlı görünüyor ve daha da kötüsü." Evet, ifade daha iyi olabilirdi. Kilide eşzamanlı olarak erişen ve böylece çekişme yaratan iş parçacıkları olarak "eşzamanlı erişilen" demek istedim.
ipavlu

20

Bu performansı hakkında sorgu cevap vermez, ama .NET Framework bir teklif yok diyebilirim Interlocked.Addsize eklentiniz sağlayacak yöntem amountsizin için donemanuel olarak başka bir nesne üzerinde kilitlemeden üyesi.


1
Evet, bu muhtemelen en iyi cevap. Ancak esas olarak daha kısa ve daha temiz kod nedeniyle. Hızdaki farkın fark edilmesi pek olası değildir.
Henk Holterman

bu cevap için teşekkürler. Kilitlerle daha çok şey yapıyorum. Eklenen girişler pek çoğundan biridir. Öneriyi seviyorum, bundan sonra kullanacağım.
Kees C.Bakker

Kilitsiz kod potansiyel olarak daha hızlı olsa bile kilitleri düzeltmek çok çok daha kolaydır. Interlocked.Add, senkronizasyon olmadan + = ile aynı sorunlara sahiptir.
hangar

10

lock (Monitor.Enter / Exit), Waithandle veya Mutex gibi alternatiflere göre çok ucuzdur.

Peki ya (biraz) yavaş olsaydı, hatalı sonuçlara sahip hızlı bir programa sahip olmayı tercih eder miydiniz?


5
Haha ... Hızlı program ve iyi sonuçlar için gidiyordum.
Kees C.Bakker

@ henk-holterman İfadelerinizle ilgili birden fazla sorun var: Birincisi , bu soru ve cevapların açıkça gösterdiği gibi, sadece tek iş parçacıklı ortamda geçerli olan 50ns hakkında efsane belirten insanlar bile, kilidin genel performans üzerindeki etkilerine dair düşük bir anlayış var. İkincisi , ifadeniz burada ve yıllarca kalacak ve bu arada, işlemciler çekirdeklerde büyümüştür, ancak çekirdeklerin hızı çok fazla değildir. ** Üçlü ** uygulamalar zamanla yalnızca daha karmaşık hale gelir ve sonra birçok çekirdek ortamında kilitleniyor ve sayı artıyor, 2,4,8,10,20,16,32
ipavlu

Her zamanki yaklaşımım, mümkün olduğunca az etkileşimle gevşek bir şekilde eşleştirilmiş bir şekilde senkronizasyon oluşturmaktır. Bu, kilitsiz veri yapılarına çok hızlı gider. Geliştirmeyi basitleştirmek için spinlock etrafındaki kod sarmalayıcılarımı yaptım ve TPL özel eşzamanlı koleksiyonlara sahip olsa bile, biraz daha fazla kontrole ve bazen de bazı kodların çalıştırılmasına ihtiyacım olduğundan, liste, dizi, sözlük ve kuyruk etrafında kendi spin kilitli koleksiyonları geliştirdim spinlock. Size söyleyebilirim, bu mümkündür ve TPL koleksiyonlarının yapamayacağı birden fazla senaryoyu çözmenize olanak tanır ve büyük performans / verim kazancı sağlar.
ipavlu

7

Sıkı bir döngüdeki bir kilidin maliyeti, kilitsiz bir alternatife kıyasla çok büyüktür. Birçok kez döngü oluşturabilir ve yine de bir kilitten daha verimli olabilirsiniz. Kilitsiz kuyrukların bu kadar verimli olmasının nedeni budur.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LockPerformanceConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            const int LoopCount = (int) (100 * 1e6);
            int counter = 0;

            for (int repetition = 0; repetition < 5; repetition++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++)
                    lock (stopwatch)
                        counter = i;
                stopwatch.Stop();
                Console.WriteLine("With lock: {0}", stopwatch.ElapsedMilliseconds);

                stopwatch.Reset();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++)
                    counter = i;
                stopwatch.Stop();
                Console.WriteLine("Without lock: {0}", stopwatch.ElapsedMilliseconds);
            }

            Console.ReadKey();
        }
    }
}

Çıktı:

With lock: 2013
Without lock: 211
With lock: 2002
Without lock: 210
With lock: 1989
Without lock: 210
With lock: 1987
Without lock: 207
With lock: 1988
Without lock: 208

4
Bu kötü bir örnek olabilir çünkü döngünüz tek bir değişken atama dışında hiçbir şey yapmaz ve bir kilit en az 2 işlev çağrısıdır. Ayrıca, aldığınız kilit başına 20ns o kadar da kötü değil.
Zar Shardan

5

"Maliyet" i tanımlamanın birkaç farklı yolu vardır. Kilidi almanın ve serbest bırakmanın gerçek ek yükü vardır; Jake'in yazdığı gibi, bu işlem milyonlarca kez yapılmadıkça bu önemsizdir.

Daha da ilgisi, bunun yürütme akışı üzerindeki etkisidir. Bu kod, bir seferde yalnızca bir iş parçacığı tarafından girilebilir. Bu işlemi düzenli olarak gerçekleştiren 5 iş parçacığına sahipseniz, bunlardan 4'ü kilidin serbest bırakılmasını bekler ve ardından kilit serbest bırakıldıktan sonra bu kod parçasını girmesi planlanan ilk iş parçacığı olur. Yani, algoritmanız önemli ölçüde zarar görecek. Ne kadarı algoritmaya ve operasyonun ne sıklıkla çağrıldığına bağlıdır .. Yarış koşullarını ortaya koymadan bundan gerçekten kaçınamazsınız, ancak kilitli koda gelen çağrıların sayısını en aza indirerek bunu iyileştirebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.