C # Bộ đếm nhanh (est) an toàn


147

Cách để có được bộ đếm an toàn luồng trong C # với hiệu suất tốt nhất có thể là gì?

Điều này là đơn giản như nó được:

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

Nhưng có những lựa chọn thay thế nhanh hơn?

Câu trả lời:



108

Theo khuyến cáo của người khác, Interlocked.Incrementsẽ có hiệu suất tốt hơn lock(). Chỉ cần nhìn vào IL và hội nơi bạn sẽ thấy Incrementbiến thành câu lệnh "khóa xe buýt" và biến của nó được tăng trực tiếp (x86) hoặc "thêm" vào (x64).

Câu lệnh "khóa xe buýt" này khóa xe buýt để ngăn CPU khác truy cập vào xe buýt trong khi CPU gọi thực hiện hoạt động. Bây giờ, hãy xem lock()IL của tuyên bố C # . Tại đây bạn sẽ thấy các cuộc gọi đến Monitorđể bắt đầu hoặc kết thúc một phần.

Nói cách khác, lock()câu lệnh .Net đang làm nhiều hơn so với .Net Interlocked.Increment.

Vì vậy, nếu tất cả những gì bạn muốn làm là tăng một biến, Interlock.Incrementsẽ nhanh hơn. Xem lại tất cả các phương pháp lồng vào nhau để xem các hoạt động nguyên tử khác nhau có sẵn và để tìm ra các phương pháp phù hợp với nhu cầu của bạn. Sử dụng lock()khi bạn muốn thực hiện những việc phức tạp hơn như tăng / giảm liên quan đến nhau hoặc để tuần tự hóa quyền truy cập vào các tài nguyên phức tạp hơn số nguyên.


3
-1 để biết chi tiết thực hiện. Đúng là khóa chậm hơn so với op nguyên tử, nhưng điều này không liên quan gì đến IL. Các cuộc gọi chức năng đó sẽ nhanh hơn một nguyên tử nếu không phải vì ngữ nghĩa của chúng, vốn không phải là yêu cầu vốn có của IL.
Cún con



1

Như đã đề cập sử dụng Interlocked.Increment

Mã ví dụ từ MS:

Ví dụ sau đây xác định có bao nhiêu số ngẫu nhiên nằm trong khoảng từ 0 đến 1.000 được yêu cầu để tạo 1.000 số ngẫu nhiên có giá trị trung điểm. Để theo dõi số lượng giá trị trung điểm, một biến, midpointCount, được đặt bằng 0 và tăng lên mỗi khi trình tạo số ngẫu nhiên trả về giá trị trung điểm cho đến khi đạt 10.000. Vì ba luồng tạo ra các số ngẫu nhiên, phương thức Tăng (Int32) được gọi để đảm bảo rằng nhiều luồng không cập nhật đồng thời midpointCount. Lưu ý rằng khóa cũng được sử dụng để bảo vệ trình tạo số ngẫu nhiên và đối tượng CountdownEvent được sử dụng để đảm bảo rằng phương thức Main không hoàn thành thực thi trước ba luồng.

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

Ví dụ sau tương tự như ví dụ trước, ngoại trừ việc nó sử dụng lớp Nhiệm vụ thay vì thủ tục luồng để tạo 50.000 số nguyên trung điểm ngẫu nhiên. Trong ví dụ này, một biểu thức lambda thay thế thủ tục luồng GenerateNumbers và lệnh gọi phương thức Task.WaitAll loại bỏ sự cần thiết của đối tượng CountdownEvent.

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

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.