C # İş parçacığı güvenli hızlı (est) sayaç


147

Mümkün olan en iyi performansa sahip bir iş parçacığı güvenli sayacı C # elde etmenin yolu nedir?

Bu olabildiğince basit:

public static long GetNextValue()
{
    long result;
    lock (LOCK)
    {
        result = COUNTER++;
    }
    return result;
}

Ancak daha hızlı alternatifler var mı?

Yanıtlar:



108

Başkaları tarafından önerildiği gibi, Interlocked.Incrementperformans daha iyi olacaktır lock(). Sadece Increment"bus lock" deyimine dönüştüğünü ve değişkeninin doğrudan (x86) veya (x64) 'e "eklendiğini" göreceğiniz IL ve Assembly'ye bir göz atın .

Bu "bus lock" ifadesi, çağrılan CPU çalışırken başka bir CPU'nun veri yoluna erişmesini önlemek için veri yolunu kilitler. Şimdi, C # lock()ifadesinin IL'sine bir göz atın . Burada Monitorbir bölümü başlatmak veya bitirmek için yapılan çağrıları göreceksiniz .

Başka bir deyişle, .Net lock()deyimi .Net'ten çok daha fazlasını yapıyor Interlocked.Increment.

Yani, tüm yapmak istediğiniz bir değişkeni artırmak ise, Interlock.Incrementdaha hızlı olacaktır. Mevcut çeşitli atomik işlemleri görmek ve ihtiyaçlarınıza uygun olanları bulmak için tüm Kilitli yöntemleri gözden geçirin. lock()Birden fazla birbiriyle ilişkili artış / azalma gibi daha karmaşık şeyler yapmak veya tamsayılardan daha karmaşık olan kaynaklara erişimi serileştirmek istediğinizde kullanın .


3
Uygulama ayrıntıları için -1. Kilitlemenin atomik bir işlemden çok daha yavaş olduğu doğrudur, ancak bunun IL ile ilgisi yoktur. Bu işlev çağrıları, anlamsalları için olmasa bile, IL'den doğal olarak gerekli olmayan bir atomik işlemden çok daha hızlı olacaktır.
Köpek yavrusu



1

Daha önce de belirtildiği gibi kullanım Interlocked.Increment

MS kod örneği:

Aşağıdaki örnek, bir orta nokta değerine sahip 1.000 rasgele sayı oluşturmak için 0 ile 1.000 arasında kaç rasgele sayının gerektiğini belirler. Orta nokta değerlerinin sayısını izlemek için, bir değişken, midpointCount, 0'a eşit olarak ayarlanır ve rastgele sayı üretecinin 10.000'e ulaşana kadar bir orta nokta değeri döndürdüğü her seferde artırılır. Üç iş parçacığı rasgele sayılar ürettiğinden, birden çok iş parçacığının midpointCount'u eşzamanlı olarak güncellemediğinden emin olmak için Increment (Int32) yöntemi çağrılır. Rastgele sayı üretecini korumak için bir kilit de kullanıldığını ve Main yönteminin üç iş parçacığından önce yürütmeyi bitirmediğinden emin olmak için bir CountdownEvent nesnesi kullanıldığını unutmayın.

using System;
using System.Threading;

public class Example
{
   const int LOWERBOUND = 0;
   const int UPPERBOUND = 1001;

   static Object lockObj = new Object();
   static Random rnd = new Random();
   static CountdownEvent cte;

   static int totalCount = 0;
   static int totalMidpoint = 0;
   static int midpointCount = 0;

   public static void Main()
   {
      cte = new CountdownEvent(1);
      // Start three threads. 
      for (int ctr = 0; ctr <= 2; ctr++) {
         cte.AddCount();
         Thread th = new Thread(GenerateNumbers);
         th.Name = "Thread" + ctr.ToString();
         th.Start();
      }
      cte.Signal();
      cte.Wait();
      Console.WriteLine();
      Console.WriteLine("Total midpoint values:  {0,10:N0} ({1:P3})",
                        totalMidpoint, totalMidpoint/((double)totalCount));
      Console.WriteLine("Total number of values: {0,10:N0}", 
                        totalCount);                  
   }

   private static void GenerateNumbers()
   {
      int midpoint = (UPPERBOUND - LOWERBOUND) / 2;
      int value = 0;
      int total = 0;
      int midpt = 0;

      do {
         lock (lockObj) {
            value = rnd.Next(LOWERBOUND, UPPERBOUND);
         }
         if (value == midpoint) { 
            Interlocked.Increment(ref midpointCount);
            midpt++;
         }
         total++;    
      } while (midpointCount < 10000);

      Interlocked.Add(ref totalCount, total);
      Interlocked.Add(ref totalMidpoint, midpt);

      string s = String.Format("Thread {0}:\n", Thread.CurrentThread.Name) +
                 String.Format("   Random Numbers: {0:N0}\n", total) + 
                 String.Format("   Midpoint values: {0:N0} ({1:P3})", midpt, 
                               ((double) midpt)/total);
      Console.WriteLine(s);
      cte.Signal();
   }
}
// The example displays output like the following:
//       Thread Thread2:
//          Random Numbers: 2,776,674
//          Midpoint values: 2,773 (0.100 %)
//       Thread Thread1:
//          Random Numbers: 4,876,100
//          Midpoint values: 4,873 (0.100 %)
//       Thread Thread0:
//          Random Numbers: 2,312,310
//          Midpoint values: 2,354 (0.102 %)
//       
//       Total midpoint values:      10,000 (0.100 %)
//       Total number of values:  9,965,084

Aşağıdaki örnek, öncekine benzer, ancak 50.000 rasgele orta nokta tamsayıları oluşturmak için bir iş parçacığı yordamı yerine Görev sınıfını kullanmasıdır. Bu örnekte, lambda ifadesi GenerateNumbers iş parçacığı yordamının yerini alır ve Task.WaitAll yöntemine yapılan çağrı CountdownEvent nesnesine olan gereksinimi ortadan kaldırır.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   const int LOWERBOUND = 0;
   const int UPPERBOUND = 1001;

   static Object lockObj = new Object();
   static Random rnd = new Random();

   static int totalCount = 0;
   static int totalMidpoint = 0;
   static int midpointCount = 0;

   public static void Main()
   {
      List<Task> tasks = new List<Task>();
      // Start three tasks. 
      for (int ctr = 0; ctr <= 2; ctr++) 
         tasks.Add(Task.Run( () => { int midpoint = (UPPERBOUND - LOWERBOUND) / 2;
                                     int value = 0;
                                     int total = 0;
                                     int midpt = 0;

                                     do {
                                        lock (lockObj) {
                                           value = rnd.Next(LOWERBOUND, UPPERBOUND);
                                        }
                                        if (value == midpoint) { 
                                           Interlocked.Increment(ref midpointCount);
                                           midpt++;
                                        }
                                        total++;    
                                     } while (midpointCount < 50000);

                                     Interlocked.Add(ref totalCount, total);
                                     Interlocked.Add(ref totalMidpoint, midpt);

                                     string s = String.Format("Task {0}:\n", Task.CurrentId) +
                                                String.Format("   Random Numbers: {0:N0}\n", total) + 
                                                String.Format("   Midpoint values: {0:N0} ({1:P3})", midpt, 
                                                              ((double) midpt)/total);
                                     Console.WriteLine(s); } ));

      Task.WaitAll(tasks.ToArray());
      Console.WriteLine();
      Console.WriteLine("Total midpoint values:  {0,10:N0} ({1:P3})",
                        totalMidpoint, totalMidpoint/((double)totalCount));
      Console.WriteLine("Total number of values: {0,10:N0}", 
                        totalCount);                  
   }
}
// The example displays output like the following:
//       Task 3:
//          Random Numbers: 10,855,250
//          Midpoint values: 10,823 (0.100 %)
//       Task 1:
//          Random Numbers: 15,243,703
//          Midpoint values: 15,110 (0.099 %)
//       Task 2:
//          Random Numbers: 24,107,425
//          Midpoint values: 24,067 (0.100 %)
//       
//       Total midpoint values:      50,000 (0.100 %)
//       Total number of values: 50,206,378

https://docs.microsoft.com/en-us/dotnet/api/system.threading.interlocked.increment?view=netcore-3.0

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.