我正在将现有应用程序移植到C#,并希望尽可能提高性能.许多现有的循环计数器和数组引用被定义为System.UInt32,而不是我将使用的Int32.
使用UInt32和Int32有任何显着的性能差异吗?
简短的回答是"不会.任何性能影响都可以忽略不计".
正确答案是"这取决于".
一个更好的问题是,"当我确定我不需要标志时,我应该使用uint吗?"
您无法就性能给出明确的"是"或"否"的原因是因为目标平台最终将决定性能.也就是说,性能取决于将要执行代码的任何处理器以及可用的指令.您的.NET代码编译为中间语言(IL或字节码).然后,这些指令由Just-In-Time(JIT)编译器编译到目标平台,作为公共语言运行时(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的一部分来检查编译器生成的程序集中的代码.
在这里,我们可以看到C#编译器支持有符号整数,并且实际上将大多数操作本身作为有符号整数执行,并且在比较分支(也就是跳转或if)时,只将内存中的值视为unsigned.尽管我们在TestUnsigned中对迭代器和累加器都使用无符号整数,但代码几乎与TestSigned方法完全相同,只有一条指令:IL_0016.快速浏览ECMA规范描述了不同之处:
blt.un.s:如果小于(无符号或无序),短格式,则分支到目标.
blt.s:如果小于,则缩小目标.
作为这样一种通用指令,可以安全地假设大多数现代高功率处理器都具有用于两种操作的硬件指令,并且它们很可能在相同数量的周期中执行,但这不能保证.低功耗处理器可能具有较少的指令,并且没有用于unsigned int的分支.在这种情况下,JIT编译器可能必须发出多个硬件指令(例如,首先进行转换,然后进行分支)以执行blt.un.s IL指令.即使是这种情况,这些附加说明也是基本的,可能不会显着影响性能.
因此,就性能而言,长期答案是"使用有符号或无符号整数之间根本不存在性能差异.如果存在差异,则可能忽略不计."
所以后来如果性能是相同的,下一个合乎逻辑的问题是,"我应该用一个无符号的价值时,我敢肯定,我并不需要一个标志?"
这里要考虑两件事情:第一,无符号整数不符合CLS,这意味着你可能会遇到的问题,如果你暴露的无符号整数作为API的一部分到另一个程序会消耗(比如,如果你是分发可重用的库).其次,.NET中的大多数操作,包括BCL公开的方法签名(由于上述原因),都使用有符号整数.因此,如果您计划实际使用无符号整数,您可能会发现自己投了很多.这将导致非常小的性能损失,并会使您的代码变得更加混乱.最后,它可能不值得.
TLDR; 回到我的C++时代,我会说"使用最合适的东西,然后让编译器对其余部分进行排序." C#是不是很切和干,所以我会说这对于.NET:有在x86/x64的一个符号和无符号整数之间真的没有性能上的差异,但大多数操作需要一个有符号整数,所以除非你真的很需要将值限制为正值或者你真的需要符号位吃的额外范围,坚持使用有符号整数.你的代码最终会更干净.
除了处理器级别的有符号和无符号算术之间可能存在的差异之外,我认为没有任何性能考虑,但在那一点上我认为差异没有实际意义.
更大的区别在于CLS合规性,因为无符号类型不符合CLS,因为并非所有语言都支持它们.