2011-07-23 69 views
6

Tôi có một phương pháp đơn giản chuyển đổi một mảng từ kiểu này sang kiểu khác. Tôi muốn tìm ra phương pháp nào là nhanh nhất. Nhưng cho đến nay tôi nhận được kết quả khác nhau mà từ đó tôi không thể kết luận phương pháp nào thực sự nhanh hơn theo đó lề.Tại sao các phép đo hiệu suất lại khác nhau?

Vì chuyển đổi chỉ phân bổ bộ nhớ, đọc mảng và chuyển đổi các giá trị, tôi ngạc nhiên rằng các giá trị không ổn định hơn. Tôi muốn biết làm thế nào tôi có thể thực hiện các phép đo chính xác có ý nghĩa và không thay đổi từ ngày này sang ngày khác. Sự khác biệt là khoảng 20% ​​từ ngày này sang ngày khác. Có sự khác biệt về khóa học giữa JITer của .NET 3.5 và 4.0, chế độ gỡ lỗi và phát hành, không chạy tệp thực thi dưới trình gỡ rối (vô hiệu hóa tối ưu hóa JIT cho đến khi bạn tắt nó), tạo mã trình biên dịch C# giữa DEBUG và XIN (chủ yếu là hoạt động nop và nhiều biến tạm thời trong mã IL).

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 

namespace PerfTest 
{ 
    class Program 
    { 
     const int RUNS = 10 * 1000 * 1000; 


     static void Main(string[] args) 
     { 
      int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43 }; 

      var s2 = Stopwatch.StartNew(); 
      for (int i = 0; i < RUNS; i++) 
      { 
       float[] arr = Cast(array); 
      } 
      s2.Stop(); 
      GC.Collect(); 

      var s3 = Stopwatch.StartNew(); 
      for (int i = 0; i < RUNS; i++) 
      { 
       float[] arr = Cast2(array); 
      } 
      s3.Stop(); 
      GC.Collect(); 

      var s4 = Stopwatch.StartNew(); 
      for (int i = 0; i < RUNS; i++) 
      { 
       var arr = CastSafe(array); 
      } 
      s4.Stop(); 


      Console.WriteLine("Times: {0} {1} {2}", s2.ElapsedMilliseconds, s3.ElapsedMilliseconds, s4.ElapsedMilliseconds); 
     } 

     // Referece cast implementation to check performance 
     public static unsafe float[] Cast(int[] input) 
     { 
      int N = input.Length; 
      float[] output = new float[N]; 

      fixed (int* pIStart = &input[0]) 
      { 
       int* pI = pIStart; 
       fixed (float* pOStart = &output[0]) 
       { 
        float* pO = pOStart; 

        for (int i = 0; i < N; i++) 
        { 
         *pO = (float)*pI; 
         pI++; 
         pO++; 
        } 
       } 
      } 

      return output; 
     } 

     // Referece cast implementation to check performance 
     public static unsafe float[] Cast2(int[] input) 
     { 
      int N = input.Length; 
      float[] output = new float[N]; 
      fixed (int* pIStart = &input[0]) 
      { 
       int* pI = pIStart; 
       fixed (float* pOStart = &output[0]) 
       { 
        float* pO = pOStart; 

        for (int i = 0; i < N; i++) 
        { 
         pO[i] = (float) pI[i]; 
        } 
       } 
      } 

      return output; 
     } 
     public static float[] CastSafe(int[] input) 
     { 
      int N = input.Length; 
      float[] output = new float[N]; 

      for (int i = 0; i < input.Length; i++) 
      { 
       output[i] = (float)input[i]; 
      } 

      return output; 
     } 
    } 
} 

tôi làm được rồi

  • Times: 1257 1388 1180
  • Times: 1331 1428 1267
  • Times: 1337 1435 1267
  • Times: 1208 1414 1145

Từ đây nó trông giống như biến thể an toàn câm nhanh hơn bất kỳ biến thể không an toàn mặc dù giới hạn kiểm tra loại bỏ các phương pháp không an toàn nên làm cho nó ít nhất là nhanh chóng nếu không nhanh hơn. Chỉ để cho vui, tôi cũng đã biên dịch cùng một mã IL thông qua LCG (DynamicMethod) mà dường như thậm chí còn chậm hơn bất kỳ phương pháp nào mặc dù chi phí bổ sung của cuộc gọi đại biểu dường như không đóng vai trò lớn như vậy ở đây.

Vòng lặp for thực hiện mã này 10 triệu lần để tạo ra kết quả ổn định. Tại sao tôi thấy bất kỳ sự khác biệt nào ở đây? Sử dụng thời gian thực là ưu tiên quá trình cũng không giúp được (thực thi psexec -realtime). Làm cách nào tôi có thể nhận được số liệu đáng tin cậy?

xét nghiệm của tôi đã bao gồm

  • máy kép Quad Core
  • Windows 7 32/64 phiên bản chút
  • .NET Framework 3,5/4,0
  • 32/64 bit của thực thi.

Nếu tôi sử dụng profiler Tôi không chắc chắn liệu anh ấy có làm méo mó các phép đo hơn nữa hay không. Kể từ khi ông làm gián đoạn ứng dụng của tôi theo thời gian để có được ngăn xếp cuộc gọi, ông chắc chắn sẽ tiêu diệt bất kỳ địa phương bộ nhớ cache mà có thể hỗ trợ hiệu suất. Nếu có bất kỳ cách tiếp cận nào với vùng nhớ cache (dữ liệu) tốt hơn thì tôi sẽ không thể tìm ra nó với một trình lược tả.

Chỉnh sửa1: Để xem xét rằng tôi không có hệ điều hành thời gian thực, tôi hiện đang lấy mẫu phép đo. Vì đối với một chủ đề, tôi có một cửa sổ thời gian 15ms được cấp cho Bộ lập lịch Windows, tôi có thể giữ lại Trình lập lịch biểu nếu tôi đo ngắn hơn 15ms. Nếu tôi đo quá sớm tôi sẽ kết thúc với số lượng rất nhỏ mà sẽ không cho tôi biết nhiều.

Để có được các giá trị ổn định, tôi cần một khoảng thời gian đủ dài để cho hệ điều hành làm bất cứ điều gì nó làm một cách thường xuyên. Các thử nghiệm theo kinh nghiệm đã chỉ ra rằng 30 giây là khoảng thời gian tốt mà một thước đo cần thực hiện.

Khoảng thời gian này sau đó được chia thành các khoảng thời gian mẫu dưới 15 mili giây. Sau đó, tôi sẽ nhận được thông tin thời gian cho mỗi mẫu. Từ các mẫu tôi có thể trích xuất min/max và trung bình. Bằng cách này tôi cũng có thể thấy hiệu ứng khởi tạo thời gian đầu tiên. Mã bây giờ trông như thế này

class Program 
{ 
    const int RUNS = 100 * 1000 * 1000; // 100 million runs will take about 30s 
    const int RunsPerSample = 100;  // 100 runs for on sample is about 0,01ms << 15ms 

    static void Main(string[] args) 
    { 
     int[] array = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43 }; 
     long[] sampleTimes = new long [RUNS/RunsPerSample]; 

     int sample = 0; 
     for (int i = 0; i < RUNS; i+=RunsPerSample) 
     { 
      var sw = Stopwatch.StartNew(); 
      for (int j = i; j < i+RunsPerSample; j++) 
      { 
       float[] arr = Cast(array); 
      } 
      sw.Stop(); 
      sampleTimes[sample] = sw.ElapsedTicks; 
      sample++; 
     } 
     Console.WriteLine("SampleSize: {0}, Min {1}, Max {2}, Average {3}", 
      RunsPerSample, sampleTimes.Min(), sampleTimes.Max(), sampleTimes.Average()); 

Các giá trị từ những thử nghiệm này làm vẫn khác nhau (< 10%) nhưng tôi nghĩ rằng nếu tôi có thể tạo một biểu đồ histogram của các giá trị của tôi và thả các giá trị 10% cao nhất mà có nhiều khả năng do hệ điều hành, GC, ... tôi có thể nhận được những con số thực sự ổn định mà tôi có thể tin tưởng.

SampleSize: 100, Min 25, Max 86400, Trung bình 28,614631

  • SampleSize: 100, tối thiểu là 24, Max 86.027, trung bình 28,762608
  • SampleSize: 100, Tối thiểu 25, tối đa 49.523 , trung bình 32,102037
  • SampleSize: 100, tối thiểu là 24, Max 48.687, trung bình 32,030088

Edit2: các biểu đồ cho thấy measu giá trị màu đỏ không phải ngẫu nhiên. Họ trông giống như một Landau distribution mà nên cho tôi với các thuật toán gần đúng đúng giá trị ổn định. Tôi muốn trong .NET sẽ tồn tại một cái gì đó như ROOT, nơi tôi có thể tương tác phù hợp với chức năng phân phối phù hợp với dữ liệu của tôi và nhận được kết quả.

Measured Values Histogram

Mã để tạo ra các biểu đồ với các MSChart điều khiển là dưới đây:

using System.Collections.Generic; 
using System.Drawing; 
using System.Linq; 
using System.Windows.Forms; 
using System.Windows.Forms.DataVisualization.Charting; 

namespace ConsoleApplication4 
{ 
    public partial class Histogram : Form 
    { 
     public Histogram(long [] sampleTimes) 
     { 
      InitializeComponent(); 

      Series histogramSeries = cHistogram.Series.Add("Histogram"); 

      // Set new series chart type and other attributes 
      histogramSeries.ChartType = SeriesChartType.Column; 
      histogramSeries.BorderColor = Color.Black; 
      histogramSeries.BorderWidth = 1; 
      histogramSeries.BorderDashStyle = ChartDashStyle.Solid; 

      var filtered = RemoveHighValues(sampleTimes, 40); 
      KeyValuePair<long,int>[] histoData = GenerateHistogram(filtered); 

      ChartArea chartArea = cHistogram.ChartAreas[histogramSeries.ChartArea]; 
      chartArea.AxisY.Title = "Frequency"; 

      chartArea.AxisX.Minimum = histoData.Min(x=>x.Key); 
      chartArea.AxisX.Maximum = histoData.Max(x=>x.Key); 

      foreach (var v in histoData) 
      { 
       histogramSeries.Points.Add(new DataPoint(v.Key, v.Value)); 
      } 

      chartArea.AxisY.Minimum = 0; 
      chartArea.AxisY.Maximum = histoData[0].Value + 100; 
     } 

     // Count the occurence of each value of input and return an array with the value as key and its count as value 
     // as ordered list starting with the highest counts. 
     KeyValuePair<long,int>[] GenerateHistogram(long [] input) 
     { 
      Dictionary<long, int> counts = new Dictionary<long, int>(); 
      foreach (var value in input) 
      { 
       int old = 0; 
       if (!counts.TryGetValue(value, out old)) 
       { 
        counts[value] = 0; 
       } 
       counts[value] = ++old; 
      } 

      var orderedCounts = (from x in counts 
           orderby x.Value descending 
           select x).ToArray(); 

      return orderedCounts; 
     } 

     long[] RemoveHighValues(long[] input, int maxDifference) 
     { 
      var min = input.Min(); 
      var max = input.Max(); 

      long[] filtered = input; 

      while (max - min > maxDifference) // remove all values wich differ by more than maxDifference ticks 
      { 
       filtered = input.Where(x => x < max).ToArray(); 
       max = filtered.Max(); 
      } 

      return filtered; 

     } 
    } 
} 

Trả lời

1

Tôi đã sửa đổi câu hỏi ban đầu của mình với kết quả là số không phải ngẫu nhiên nhưng theo phân phối (trông giống như bản phân phối Landau).

0

Tôi đoán nó sẽ chạy với mono trong Linux? Để tránh ảnh hưởng của môi trường đa nhiệm, bạn có thể bắt đầu bất kỳ chương trình nào bằng

time program 

và nhận số đo, thời gian sử dụng chương trình của bạn.

Bạn cũng đang đo giai đoạn khởi động và thời gian tải, nhưng nếu bạn có đủ yếu tố trong vòng lặp, nó không phải là vấn đề lớn. Có thể có một chương trình tương đương trên nền tảng windows?

+0

tôi sẽ nghĩ rằng chạy dưới mono lý thuyết có thể cung cấp cho bạn kết quả rất khác nhau từ chạy dưới cửa sổ. – Jeff

+0

Có, nhưng có thể số ổn định hơn, điều này có thể thú vị, nếu bạn quan tâm, ngay cả khi tăng hiệu suất nhỏ. –

+0

Tôi không quan tâm đến thời gian CPU. Vì tôi truy cập bộ nhớ từ L1, L2 hoặc bộ nhớ thực (cache miss), tôi cần đo độ trễ giữa các cách tiếp cận khác nhau. Tôi có thể ví dụ tiêu thụ nhiều CPU hơn khi tôi có thể sử dụng một bộ nhớ đắt tiền truy cập (tính toán chỉ số mảng bên trong thanh ghi thay vì sử dụng biến tạm thời). Đối với điều này tôi cần tổng thời gian chạy. –

4

Bạn đang nói về sự khác biệt trung bình khoảng một phần trăm của một nano giây cho mỗi cuộc gọi phương thức. Windows không yêu cầu hệ điều hành thời gian thực; các phép đo này ổn định như bạn sẽ nhận được.

Và, nhân tiện, the jitter will eliminate the bounds check inside your CastSafe method. Tôi sẽ rất ngạc nhiên nếu bạn có thể tìm thấy bất cứ điều gì nhanh hơn thế.

(Nếu nút cổ chai là CPU thì bạn có thể cải thiện hiệu suất bằng cách sử dụng Parallel.For thay vì vòng lặp for đơn giản, nhưng để xác định rằng bạn cần kiểm tra dữ liệu trong thế giới thực. rất khác nhau đối với một mảng 43 ints so với một mảng 43.000.000 ints.)

+0

Bạn ngụ ý rằng tôi thử với Bộ xử lý 2GHz của mình để đo lường những thứ dưới 1/100ns. Điều đó có nghĩa là tôi cố gắng đo lường với độ chính xác của 1/50 hướng dẫn (trung bình). Đây không phải là trường hợp. Tôi muốn đo đủ ngắn để hạn chế ảnh hưởng của hệ điều hành và đủ dài để trung bình ra nhiễu ngẫu nhiên gây ra bởi bộ lập lịch hệ điều hành. Parallel.For có nghĩa là đối với mỗi số bạn muốn chuyển đổi, bạn gọi một đại biểu. Đó là waaaay đắt tiền. –

+0

@ Alois: Đó là lý do tại sao tôi nói rằng tôi sẽ ngạc nhiên nếu bạn có thể tìm thấy bất cứ điều gì nhanh hơn phương pháp 'SafeCast' của bạn. Bạn đã làm được nhiều nhất có thể - cho rằng bạn đang sử dụng .NET trên Windows - để "hạn chế ảnh hưởng của hệ điều hành" và "tiếng ồn trung bình ngẫu nhiên gây ra bởi bộ lập lịch hệ điều hành". – LukeH

0

Stopwatch mà không phải là chính xác, hãy thử sử dụng HighResClock

http://netcode.ru/dotnet/?lang=&katID=30&skatID=261&artID=7113

không mong đợi các phép đo chính xác cho nano-giây, như ai đó đã viết, Win7 không phải là một thời gian thực OS .

Ngoài ra, sau GC.Collect(), bạn có thể muốn đặt GC.WaitForPendingFinalizers();

+0

Đồng hồ bấm giờ không bao gồm QueryPerformanceCounter/Frequency chỉ trong cùng một cách với liên kết của bạn. Tôi nhận thức được những cạm bẫy của DateTime mà chỉ có độ chính xác 16ms. Nhưng QueryPerformanceCounter cũng có vấn đề nếu bo mạch chủ không cung cấp bộ đếm thời gian chính xác cao Windows sử dụng bộ đếm thời gian của CPU, cung cấp các giá trị khác nhau trên mỗi CPU, điều này trở thành một nhức đầu trên các máy đa lõi. –

Các vấn đề liên quan