在背靠背for循环中的int,short,byte性能

(背景: 为什么我应该在C#中使用int而不是字节或short )

为了满足我自己对使用“适当大小”整数与“优化”整数的优缺点的好奇心,我编写了以下代码,这些代码强化了我以前在.Net中对int性能的真实性(并在链接中对此进行了解释)以上)它是针对int性能而不是short或byte进行优化的。

DateTime t; long a, b, c; t = DateTime.Now; for (int index = 0; index < 127; index++) { Console.WriteLine(index.ToString()); } a = DateTime.Now.Ticks - t.Ticks; t = DateTime.Now; for (short index = 0; index < 127; index++) { Console.WriteLine(index.ToString()); } b=DateTime.Now.Ticks - t.Ticks; t = DateTime.Now; for (byte index = 0; index < 127; index++) { Console.WriteLine(index.ToString()); } c=DateTime.Now.Ticks - t.Ticks; Console.WriteLine(a.ToString()); Console.WriteLine(b.ToString()); Console.WriteLine(c.ToString()); 

这在……的范围内给出了大致一致的结果。

〜95万

〜2000000

〜1700000

这与我期望看到的一致。

但是当我尝试重复每个数据类型的循环时……

 t = DateTime.Now; for (int index = 0; index < 127; index++) { Console.WriteLine(index.ToString()); } for (int index = 0; index < 127; index++) { Console.WriteLine(index.ToString()); } for (int index = 0; index < 127; index++) { Console.WriteLine(index.ToString()); } a = DateTime.Now.Ticks - t.Ticks; 

数字更像……

〜450

〜3100000

〜30万

我觉得这令人费解。 任何人都可以提供解释吗?

注意:为了比较,我喜欢将循环限制为127,因为字节值类型的范围。 这也是一种好奇心而非生产代码微优化的行为。

首先,它不是针对int性能优化的.NET,它是优化的机器 ,因为32位是本机字大小(除非你是在x64上,在这种情况下它是long或64位)。

其次,你在每个循环中写入控制台 – 这比增加和测试循环计数器要昂贵得多,所以你不会在这里测量任何真实的东西。

第三,一个byte范围最大为255,所以你可以循环254次(如果你试图做255,它会溢出而循环永远不会结束 – 但你不需要在128处停止)。

第四,你没有做足够的迭代来进行分析。 迭代128或甚至254次的紧密循环是没有意义的。 您应该做的是将byte / short / int循环放在另一个循环中,该循环迭代的次数要多得多,比如1000万,并检查结果。

最后,在计算中使用DateTime.Now会在分析时产生一些定时“噪音”。 建议(并且更容易)使用秒表类。

最重要的是,在进行有效的性能测试之前,这需要进行许多更改。


以下是我认为更准确的测试程序:

 class Program { const int TestIterations = 5000000; static void Main(string[] args) { RunTest("Byte Loop", TestByteLoop, TestIterations); RunTest("Short Loop", TestShortLoop, TestIterations); RunTest("Int Loop", TestIntLoop, TestIterations); Console.ReadLine(); } static void RunTest(string testName, Action action, int iterations) { Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < iterations; i++) { action(); } sw.Stop(); Console.WriteLine("{0}: Elapsed Time = {1}", testName, sw.Elapsed); } static void TestByteLoop() { int x = 0; for (byte b = 0; b < 255; b++) ++x; } static void TestShortLoop() { int x = 0; for (short s = 0; s < 255; s++) ++x; } static void TestIntLoop() { int x = 0; for (int i = 0; i < 255; i++) ++x; } } 

这会在一个更大的循环(500万次迭代)中运行每个循环,并在循环内执行非常简单的操作(递增变量)。 我的结果是:

字节循环:经过时间= 00:00:03.8949910
短循环:经过时间= 00:00:03.9098782
Int Loop:经过时间= 00:00:03.2986990

所以,没有明显的区别。

此外,确保您在发布模式下进行配置,许多人忘记并在调试模式下进行测试,这将显着降低准确性。

大部分时间可能花在写入控制台上。 尝试在循环中做一些不同的事情……

另外:

  • 使用DateTime.Now是一种衡量时间的坏方法。 请改用System.Diagnostics.Stopwatch
  • 一旦你摆脱了Console.WriteLine调用,127次迭代的循环将太短而无法衡量。 您需要多次运行循环以获得合理的测量。

这是我的基准:

 using System; using System.Diagnostics; public static class Test { const int Iterations = 100000; static void Main(string[] args) { Measure(ByteLoop); Measure(ShortLoop); Measure(IntLoop); Measure(BackToBack); Measure(DelegateOverhead); } static void Measure(Action action) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { action(); } sw.Stop(); Console.WriteLine("{0}: {1}ms", action.Method.Name, sw.ElapsedMilliseconds); } static void ByteLoop() { for (byte index = 0; index < 127; index++) { index.ToString(); } } static void ShortLoop() { for (short index = 0; index < 127; index++) { index.ToString(); } } static void IntLoop() { for (int index = 0; index < 127; index++) { index.ToString(); } } static void BackToBack() { for (byte index = 0; index < 127; index++) { index.ToString(); } for (short index = 0; index < 127; index++) { index.ToString(); } for (int index = 0; index < 127; index++) { index.ToString(); } } static void DelegateOverhead() { // Nothing. Let's see how much // overhead there is just for calling // this repeatedly... } } 

结果如下:

 ByteLoop: 6585ms ShortLoop: 6342ms IntLoop: 6404ms BackToBack: 19757ms DelegateOverhead: 1ms 

(这是在上网本上 - 调整迭代次数,直到你得到合理的东西:)

这似乎表明它与您使用的类型基本没有显着差异。

出于好奇,我修改了Aaronaught的程序,并在x86和x64两种模式下编译。 奇怪,Int在x64中工作得更快:

86

字节循环:经过时间= 00:00:00.8636454
短循环:经过时间= 00:00:00.8795518
UShort循环:经过时间= 00:00:00.8630357
Int Loop:经过时间= 00:00:00.5184154
UInt循环:经过时间= 00:00:00.4950156
长循环:经过时间= 00:00:01.2941183
ULONG循环:经过时间= 00:00:01.3023409

64位

字节循环:经过时间= 00:00:01.0646588
短循环:经过时间= 00:00:01.0719330
UShort循环:经过时间= 00:00:01.0711545
Int Loop:经过时间= 00:00:00.2462848
UInt循环:经过时间= 00:00:00.4708777
长循环:经过时间= 00:00:00.5242272
ULONG循环:经过时间= 00:00:00.5144035

我尝试了上面的两个程序,因为他们看起来会在我的开发机器上产生不同的,可能相互矛盾的结果。

Aaronaughts测试安全带的输出

 Short Loop: Elapsed Time = 00:00:00.8299340 Byte Loop: Elapsed Time = 00:00:00.8398556 Int Loop: Elapsed Time = 00:00:00.3217386 Long Loop: Elapsed Time = 00:00:00.7816368 

整数更快

Jon的输出

 ByteLoop: 1126ms ShortLoop: 1115ms IntLoop: 1096ms BackToBack: 3283ms DelegateOverhead: 0ms 

什么都没有

Jon在结果中调用tostring有一个很大的固定常量,如果在循环中完成的工作较少,可能会隐藏可能带来的好处。 Aaronaught正在使用32位操作系统,这似乎不像我正在使用的x64钻机那样使用整数。

硬件/软件结果在Core i7 975上以3.33GHz收集,并禁用turbo,并设置核心亲和力以减少其他任务的影响。 性能设置全部设置为最大值,病毒扫描程序/不必要的后台任务暂停 Windows 7 x64终极版具有11 GB备用RAM和极少的IO活动。 在vs 2008中内置的发布配置中运行,而不附加调试器或分析器。

重复性最初为每次测试重复执行10次执行顺序。 变化可以忽略不计,所以我只发布了我的第一个结果。 在最大CPU负载下,执行时间的比率保持一致。 在考虑CPU生成和Ghz之后,在多个x64 xp xeon刀片上重复运行会得到大致相同的结果

分析 Redgate / Jetbrains / Slimtune / CLR分析器和我自己的分析器都表明结果是正确的。

调试构建使用VS中的调试设置可以像Aaronaught那样提供一致的结果。

分析.Net代码非常棘手,因为编译的字节代码运行的运行时环境可以对字节代码进行运行时优化。 在第二个示例中,JIT编译器可能发现重复的代码并创建了更优化的版本。 但是,如果没有任何关于运行时系统如何工作的详细描述,就不可能知道代码会发生什么。 根据实验尝试和猜测是愚蠢的,因为微软完全有权在任何时候重新设计JIT引擎,只要它们不破坏任何function。

控制台写入与数据的实际性能无关。 它更多地与与控制台库调用的交互有关。 建议你在那些数据大小独立的循环中做一些有趣的事情。

建议:位移,乘法,数组操作,加法,许多其他…