在C#中,使用UInt32和Int32有任何显着的性能差异

时间:2008-11-20 19:50:07

标签: c# performance int32 uint32

我正在将现有应用程序移植到C#,并希望尽可能提高性能。许多现有的循环计数器和数组引用被定义为System.UInt32,而不是我将使用的Int32。

使用UInt32与Int32有明显的性能差异吗?

8 个答案:

答案 0 :(得分:19)

简短的回答是“不会。任何性能影响都可以忽略不计”。

正确答案是“这取决于。”

一个更好的问题是,“当我确定我不需要标志时,我应该使用uint吗?”

您无法就性能给出明确的“是”或“否”的原因是因为目标平台最终将决定性能。也就是说,性能取决于将要执行代码的任何处理器以及可用的指令。您的.NET代码编译为Intermediate Language(IL或字节码)。然后,Just-In-Time(JIT)编译器将这些指令编译到目标平台,作为Common Language Runtime(CLR)的一部分。您无法控制或预测将为每个用户生成的代码。

因此,要知道硬件是性能的最终仲裁者,问题就变成了,“.NET为有符号整数与无符号整数生成的代码有多么不同?”并且“差异是否会影响我的应用程序和我的目标平台?”

回答这些问题的最佳方法是进行测试。

class Program
{
  static void Main(string[] args)
  {
    const int iterations = 100;
    Console.WriteLine($"Signed:      {Iterate(TestSigned, iterations)}");
    Console.WriteLine($"Unsigned:    {Iterate(TestUnsigned, iterations)}");
    Console.Read();
  }

  private static void TestUnsigned()
  {
    uint accumulator = 0;
    var max = (uint)Int32.MaxValue;
    for (uint i = 0; i < max; i++) ++accumulator;
  }

  static void TestSigned()
  {
    int accumulator = 0;
    var max = Int32.MaxValue;
    for (int i = 0; i < max; i++) ++accumulator;
  }

  static TimeSpan Iterate(Action action, int count)
  {
    var elapsed = TimeSpan.Zero;
    for (int i = 0; i < count; i++)
      elapsed += Time(action);
    return new TimeSpan(elapsed.Ticks / count);
  }

  static TimeSpan Time(Action action)
  {
    var sw = new Stopwatch();
    sw.Start();
    action();
    sw.Stop();
    return sw.Elapsed;
  }
}

两种测试方法 TestSigned TestUnsigned ,每种方法分别对有符号和无符号整数执行约200万次简单增量迭代。测试代码运行每次测试100次迭代并平均结果。这应该排除任何潜在的不一致。我为x64编译的i7-5960X的结果是:

Signed:      00:00:00.5066966

Unsigned:    00:00:00.5052279

这些结果几乎相同,但为了获得明确的答案,我们确实需要查看为该程序生成的字节码。我们可以使用ILDASM作为.NET SDK的一部分来检查编译器生成的程序集中的代码。

Bytecode

在这里,我们可以看到C#编译器支持有符号整数,并且实际上将大多数操作本身作为有符号整数执行,并且在比较分支(a.k.a jump或if)时,只将内存中的值视为unsigned。尽管我们在 TestUnsigned 中对迭代器和累加器使用无符号整数,但代码几乎与 TestSigned 方法完全相同,除了单个指令:的 IL_0016 即可。快速浏览ECMA spec描述了差异:

  

blt.un.s:   如果小于(无符号或无序),则缩写为目标。

     

blt.s:   如果小于,则缩小目标。

作为这样一种通用指令,可以安全地假设大多数现代高功率处理器都有两个操作的硬件指令,并且它们很可能在相同的周期内执行,但是这不能保证< / strong>即可。低功耗处理器可能具有较少的指令,并且没有用于unsigned int的分支。在这种情况下,JIT编译器可能必须发出多个硬件指令(例如,首先进行转换,然后进行分支)以执行 blt.un.s IL指令。即使是这种情况,这些附加说明也是基本的,可能不会显着影响性能。

因此,就性能而言,长期答案是“使用有符号或无符号整数之间根本不存在性能差异。如果存在差异,则可能忽略不计。”< / p>

那么如果性能相同,那么下一个逻辑问题是,“当我确定我不需要一个符号时,我应该使用无符号值吗?”

这里要考虑两件事:第一,无符号整数不是CLS-compliant,这意味着如果你将无符号整数暴露为另一个程序将使用的API的一部分,你可能遇到问题(如好像你正在分发一个可重用的库)。其次,.NET中的大多数操作,包括BCL公开的方法签名(由于上述原因),都使用有符号整数。因此,如果您计划实际使用无符号整数,您可能会发现自己投了很多。这将有一个非常小的性能影响,将使您的代码有点混乱。最后,它可能不值得。

TLDR; 回到我的C ++时代,我会说“使用最合适的东西,然后让编译器对其余内容进行排序。” C#不是那么简单,所以我会说这个用于.NET:x86 / x64上的有符号和无符号整数之间确实没有性能差异,但是大多数操作都需要有符号整数,所以除非你真的需要将值限制为正值或者你真的需要符号位吃的额外范围,坚持使用有符号整数。你的代码最终会更清洁。

答案 1 :(得分:10)

除了处理器级别的有符号和无符号算术之间可能存在差异外,我认为没有任何性能考虑因素,但此时我认为这些差异没有实际意义。

更大的区别在于CLS合规性,因为无符号类型不符合CLS,因为并非所有语言都支持它们。

答案 2 :(得分:3)

我还没有对.NET中的问题进行任何研究,但是在Win32 / C ++的旧时代,如果你想将“signed int”转换为“signed long”,那么cpu必须运行一个我要延长标志。要将“unsigned int”转换为“unsigned long”,它只是在高位字节中有零。节省的时间大约是几个时钟周期(也就是说,你需要花费数十亿次才能产生可​​观察到的差异)

答案 3 :(得分:3)

没有区别,表现明智。简单的整数计算是众所周知的,现代的cpu经过高度优化,可以快速执行。

答案 4 :(得分:1)

这些类型的优化很少值得付出努力。使用最适合该任务的数据类型,并将其保留。如果这个东西触及数据库,你可能会在数据库设计,查询语法或索引策略中找到十几个调整,这些调整会使C#中的代码优化偏差几百个数量级。

答案 5 :(得分:0)

它将以任一方式分配相同数量的内存(尽管可以存储更大的值,因为它不会为符号节省空间)。所以我怀疑你会看到“性能”差异,除非你使用大值/负值导致一个选项或另一个选项爆炸。

答案 6 :(得分:0)

这与性能而不是循环计数器的要求有关。

预计有很多迭代需要完成

        Console.WriteLine(Int32.MaxValue);      // Max interation 2147483647
        Console.WriteLine(UInt32.MaxValue);     // Max interation 4294967295

unsigned int可能是有原因的。

答案 7 :(得分:0)

我从没对循环int中使用for(int i=0;i<bla;i++)感到同情。通常,我也想使用unsigned来避免检查范围。不幸的是(在C ++中和出于C#的类似原因),建议不要使用unsigned来获得更多的比特率或确保非负性:

“使用unsigned代替int来获得更多的比特来表示正整数几乎不是一个好主意。尝试通过声明变量{{1}来确保某些值是正数}通常会被隐式转换规则打败”

第73页,来自“ C ++编程语言”,由该语言的创建者 Bjarne Stroustrup提供。

我的理解(对不起手头而感到抱歉)是,硬件制造商也倾向于针对unsigned类型进行优化。

尽管如此,执行与#Robear相同的练习还是很有趣的,但是使用integerinteger并带有一定的积极性。