2010-03-17 24 views
2

这是进行绩效分析的有效方法吗?我想精确到纳秒,并确定强制类型转换的性能:C#性能分析 - 如何计算CPU周期?

class PerformanceTest 
{ 
    static double last = 0.0; 
    static List<object> numericGenericData = new List<object>(); 
    static List<double> numericTypedData = new List<double>(); 

    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 
+2

有点旁注。也许它不需要陈述,但是当你正在做这种测试时,确保你在发布模式下编译。你不会想象在调试过程中幕后发生的事情。就像我说的,也许它不需要陈述,但它可能值得一提。 – Dested 2010-03-17 23:31:37

+0

@Dested我在释放和调试模式下试过它,输出没有差别。 – Kiril 2010-03-17 23:38:21

+0

类型转换对我来说看起来差不多是3倍。我错过了什么吗? – 2010-03-17 23:46:47

回答

8

请注意,这并不是说您正在测量,而是取消装箱。这些值始终是双倍的,没有类型转换正在进行。

您忘记了在测试之间重置秒表,因此您要重复添加以前所有测试的累计时间。如果您将报价转换为实际时间,您会发现它比运行测试所花费的时间多得多。

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

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

拆箱的数值是如此昂贵,它只有检查对象中的数据类型正确,然后获得价值。尽管这种类型已经知道了,但还是有很多工作要做。请记住,您还在测量结果的循环,计算和分配,这对两个测试都是一样的。

装箱的价值比拆箱更昂贵,因为它在堆上分配一个对象。

+0

涵盖了所有的细节。 – Kiril 2010-03-18 00:30:37

3

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

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

考虑

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

而且,你的测量包括foreach循环,看起来像50%以上我。也许90%。

+0

你说的没错,当我提出在我看到一个显著的性能差异循环秒表: 平均类型转换= 129570.35537 平均无铸字= 378055.40987 铸字几乎是4倍慢! – Kiril 2010-03-17 23:36:49

+0

除此之外,用循环内的Sw来测量秒表文物。你的结果是否可重复> – 2010-03-17 23:40:49

+0

@亨克是的,我多次运行这个应用程序(发布和调试模式),它会产生相同的结果。 – Kiril 2010-03-17 23:42:10

1

当你打电话给Stopwatch.Start时,让定时器继续从停止的地方继续运行。在重新开始之前,您需要调用Stopwatch.Reset()将定时器设置回零。就个人而言,我只是使用stopwatch = Stopwatch.StartNew(),每当我想启动计时器以避免这种混淆。

此外,您可能想在启动“时序循环”之前调用两种测试方法,以便让他们有机会“热身”这段代码并确保JIT有机会运行甚至是比赛场地。

当我在我的机器上这样做时,我发现testWithTypecasting的运行时间约为testWithoutTypeCasting的一半。

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

0

查看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 docs

0

只是一个想法,但有时相同的机器代码可以采取不同的循环次数取决于它在内存对齐来执行的,所以你可能要添加一个控制或控制。

0

不要“自己做”C#,而是用C语言编写x86-32版本,而后来通常可以使用rdtsc指令,这比OS刻度更准确。有关rdtsc的更多信息可以通过搜索stackoverflow找到。在C下,它通常可以作为内部函数或内置函数使用,并且在计算机启动后返回时钟周期数(长8字节/长__ int64 - 无符号整数)。因此,如果CPU的时钟速度为3 Ghz,则底层计数器每秒增加30亿次。除少数早期的AMD处理器外,所有多核CPU都将使其计数器保持同步。

如果C#没有它,你可能会考虑写一个非常短的C函数来从C#访问它。如果通过函数或内联访问指令,会产生很大的开销。两个背对背调用函数的区别将成为基本的测量开销。如果你正在考虑计算你的应用程序,你将不得不确定几个更复杂的开销值。

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