C#性能分析 – 如何计算CPU周期?

这是进行性能分析的有效方法吗? 我希望获得纳秒精度并确定类型转换的性能:

class PerformanceTest { static double last = 0.0; static List numericGenericData = new List(); static List numericTypedData = new List(); static void Main(string[] args) { double totalWithCasting = 0.0; double totalWithoutCasting = 0.0; for (double d = 0.0; d < 1000000.0; ++d) { numericGenericData.Add(d); numericTypedData.Add(d); } Stopwatch stopwatch = new Stopwatch(); for (int i = 0; i < 10; ++i) { stopwatch.Start(); testWithTypecasting(); stopwatch.Stop(); totalWithCasting += stopwatch.ElapsedTicks; stopwatch.Start(); testWithoutTypeCasting(); stopwatch.Stop(); totalWithoutCasting += stopwatch.ElapsedTicks; } Console.WriteLine("Avg with typecasting = {0}", (totalWithCasting/10)); Console.WriteLine("Avg without typecasting = {0}", (totalWithoutCasting/10)); Console.ReadKey(); } static void testWithTypecasting() { foreach (object o in numericGenericData) { last = ((double)o*(double)o)/200; } } static void testWithoutTypeCasting() { foreach (double d in numericTypedData) { last = (d * d)/200; } } } 

输出是:

 Avg with typecasting = 468872.3 Avg without typecasting = 501157.9 

我有点怀疑……看起来对性能的影响几乎没有。 铸造真的那么便宜吗?

更新:

 class PerformanceTest { static double last = 0.0; static object[] numericGenericData = new object[100000]; static double[] numericTypedData = new double[100000]; static Stopwatch stopwatch = new Stopwatch(); static double totalWithCasting = 0.0; static double totalWithoutCasting = 0.0; static void Main(string[] args) { for (int i = 0; i < 100000; ++i) { numericGenericData[i] = (double)i; numericTypedData[i] = (double)i; } for (int i = 0; i < 10; ++i) { stopwatch.Start(); testWithTypecasting(); stopwatch.Stop(); totalWithCasting += stopwatch.ElapsedTicks; stopwatch.Reset(); stopwatch.Start(); testWithoutTypeCasting(); stopwatch.Stop(); totalWithoutCasting += stopwatch.ElapsedTicks; stopwatch.Reset(); } Console.WriteLine("Avg with typecasting = {0}", (totalWithCasting/(10.0))); Console.WriteLine("Avg without typecasting = {0}", (totalWithoutCasting / (10.0))); Console.ReadKey(); } static void testWithTypecasting() { foreach (object o in numericGenericData) { last = ((double)o * (double)o) / 200; } } static void testWithoutTypeCasting() { foreach (double d in numericTypedData) { last = (d * d) / 200; } } } 

输出是:

 Avg with typecasting = 4791 Avg without typecasting = 3303.9 

请注意,您测量的不是类型转换,而是取消装箱。 这些值一直是双打,没有类型转换。

您忘记在测试之间重置秒表,因此您反复添加所有先前测试的累计时间。 如果将刻度转换为实际时间,您会发现它的累计时间远远超过运行测试所花费的时间。

如果你添加一个stopwatch.Reset(); 在每个stopwatch.Start();之前stopwatch.Start(); ,你得到一个更合理的结果,如:

 Avg with typecasting = 41027,1 Avg without typecasting = 20594,3 

取消装箱值并不是那么昂贵,只需要检查对象中的数据类型是否正确,然后获取值。 仍然比已知类型的工作要多得多。 请记住,您还在测量结果的循环,计算和分配,这两个测试都是相同的。

拳击值比取消装箱更昂贵,因为它在堆上分配一个对象。

1)是的,铸造通常(非常)便宜。

2)您不会在托管语言中获得纳秒精度。 或者在大多数操作系统下使用非托管语言。

考虑

  • 其他过程
  • 垃圾收集
  • 不同的JITters
  • 不同的CPU

并且,您的测量包括foreach循环,对我来说看起来像50%或更多。 也许90%。

当你打电话给秒表时,它会让计时器从它停止的任何地方继续运行。 您需要调用Stopwatch.Reset()以在再次启动之前将计时器设置回零。 就我个人而言,只要我想启动计时器以避免这种混乱,我就会使用秒表= Stopwatch.StartNew()。

此外,您可能希望在启动“计时循环”之前调用两个测试方法,以便他们有机会“热身”这段代码并确保JIT有机会运行到甚至播放领域。

当我在我的机器上执行此操作时,我发现testWithTypecasting在大约一半的时间内运行为testWithoutTypeCasting。

然而,正如所说的,演员本身不太可能是性能惩罚中最重要的部分。 testWithTypecasting方法在盒装双精度列表上运行,这意味着除了增加消耗的内存总量之外,还需要额外的间接级别来检索每个值(遵循对内存中其他位置的值的引用)。 这增加了内存访问所花费的时间,并且可能比“在演员”中花费的CPU时间更大。

查看System.Diagnostics命名空间中的性能计数器,在创建新计数器时,首先创建一个类别,然后指定要放入其中的一个或多个计数器。

  // Create a collection of type CounterCreationDataCollection. System.Diagnostics.CounterCreationDataCollection CounterDatas = new System.Diagnostics.CounterCreationDataCollection(); // Create the counters and set their properties. System.Diagnostics.CounterCreationData cdCounter1 = new System.Diagnostics.CounterCreationData(); System.Diagnostics.CounterCreationData cdCounter2 = new System.Diagnostics.CounterCreationData(); cdCounter1.CounterName = "Counter1"; cdCounter1.CounterHelp = "help string1"; cdCounter1.CounterType = System.Diagnostics.PerformanceCounterType.NumberOfItems64; cdCounter2.CounterName = "Counter2"; cdCounter2.CounterHelp = "help string 2"; cdCounter2.CounterType = System.Diagnostics.PerformanceCounterType.NumberOfItems64; // Add both counters to the collection. CounterDatas.Add(cdCounter1); CounterDatas.Add(cdCounter2); // Create the category and pass the collection to it. System.Diagnostics.PerformanceCounterCategory.Create( "Multi Counter Category", "Category help", CounterDatas); 

请参阅MSDN文档

只是一个想法但有时相同的机器代码可以根据其在内存中的对齐而执行不同的循环次数,因此您可能想要添加一个控件或控件。

不要“做”C#自己,而是用C表示x86-32以后的rdtsc指令通常可用,它比OS滴答更准确。 有关rdtsc的更多信息可以通过搜索stackoverflow找到。 在C下,它通常作为内部函数或内置函数提供,并返回自计算机启动以来的时钟周期数(8字节 – 长long / __ int64 – 无符号整数)。 因此,如果CPU的时钟速度为3 Ghz,则基础计数器每秒增加30亿次。 除了一些早期的AMD处理器之外,所有多核CPU都将使其计数器同步。

如果C#没有它,您可以考虑编写一个非常短的C函数来从C#访问它。 如果通过函数vs内联访问指令,则会产生大量开销。 对函数的两次背对背调用之间的差异将是基本的测量开销。 如果您正在考虑计量应用程序,则必须确定几个更复杂的开销值。

您可以考虑关闭CPU节能模式(并重新启动PC),因为它会降低在低活动期间馈送到CPU的时钟频率。 这是因为它导致不同核心的时间戳计数器变得不同步。