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ả.
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;
}
}
}
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
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ỏ. –
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. –