在 C# 中,使用 UInt32 与 Int32 是否有显着的性能差异
-
08-07-2019 - |
题
我正在将现有应用程序移植到 C#,并希望尽可能提高性能。许多现有的循环计数器和数组引用被定义为 System.UInt32,而不是我本来使用的 Int32。
使用 UInt32 与 Int32 是否有显着的性能差异?
解决方案
除了处理器级别的有符号和无符号算术之间可能存在差异外,我认为没有任何性能考虑因素,但此时我认为差异没有实际意义。
更大的区别在于CLS合规性,因为无符号类型不符合CLS,因为并非所有语言都支持它们。
其他提示
简短回答是<!>“否。任何性能影响都可以忽略不计<。>“。
正确的答案是<!>;它取决于。<!>
更好的问题是,<!>我应该在确定我不需要标志时使用uint吗?<!>
你无法给出明确的<!>“是的原因<!>”或<!>“;否<!>”;关于性能是因为目标平台最终将决定性能。也就是说,性能取决于将要执行代码的任何处理器以及可用的指令。您的.NET代码编译为中间语言(IL或字节码)。然后通过即时(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#编译器支持有符号整数,并且实际上将大多数操作本身作为有符号整数执行,并且在比较分支(a.k.a jump或if)时,只将内存中的值视为unsigned。尽管我们在 TestUnsigned 中对迭代器和累加器使用无符号整数,但代码几乎与 TestSigned 方法完全相同,除了单个指令:的 IL_0016 即可。快速浏览一下 ECMA规范,可以看出差异:
blt.un.s: 如果小于(无符号或无序),则缩写为目标。
blt.s: 如果小于,则缩小目标。
作为这样一种通用指令,可以安全地假设大多数现代高功率处理器都有两个操作的硬件指令,并且它们很可能在相同的周期内执行,但是这不能保证即可。低功耗处理器可能具有较少的指令,并且没有用于unsigned int的分支。在这种情况下,JIT编译器可能必须发出多个硬件指令(例如,首先进行转换,然后进行分支)以执行 blt.un.s IL指令。即使是这种情况,这些额外的指示也是基本的,可能是有害的不会对性能产生重大影响。
因此,就性能而言,长答案是<!>;使用有符号或无符号整数之间不太可能存在性能差异。如果存在差异,则可能忽略不计。<!>
那么如果性能相同,那么下一个逻辑问题是,<!>;当我确定我不需要一个符号时,我应该使用无符号值吗?<! > QUOT;
这里要考虑两件事:首先,无符号整数不是符合CLS ,这意味着如果您将无符号整数作为另一个程序将使用的API的一部分公开(例如,如果您正在分发可重复使用的库)。其次,.NET中的大多数操作,包括BCL公开的方法签名(由于上述原因),都使用有符号整数。因此,如果您计划实际使用无符号整数,您可能会发现自己投了很多。这将有一个非常小的性能影响,将使您的代码有点混乱。最后,它可能不值得。
TLDR; 回到我的C ++时代,我会说<!>“;使用最合适的东西,让编译器对其余部分进行排序。<!> quot; C#不是那么简单,所以我会说这个用于.NET:x86 / x64上的有符号和无符号整数之间确实没有性能差异,但是大多数操作都需要有符号整数,所以除非你真的需要将值限制为正值或者你真的需要符号位吃的额外范围,坚持使用有符号整数。你的代码最终会更清洁。
我没有在 .NET 中对此事进行任何研究,但在 Win32/C++ 的旧时代,如果你想将“signed int”转换为“signed long”,CPU 必须运行一个操作来扩展标志。要将“unsigned int”转换为“unsigned long”,只需在高字节中添加零即可。节省的时间约为几个时钟周期(即,您必须执行数十亿次才能获得均匀的可感知差异)
没有区别,表现明智。简单的整数计算是众所周知的,现代的cpu经过高度优化,可以快速执行。
这些类型的优化很少值得付出努力。使用最适合该任务的数据类型,并将其保留。如果这个东西触及数据库,你可能会在数据库设计,查询语法或索引策略中找到十几个调整,这些调整会使C#中的代码优化偏差几百个数量级。
它将以任一方式分配相同数量的内存(尽管可以存储更大的值,因为它不会为符号节省空间)。所以我怀疑你会看到“性能”差异,除非你使用大值/负值导致一个选项或另一个选项爆炸。
这与性能而不是循环计数器的要求有关。
预计有很多迭代需要完成
Console.WriteLine(Int32.MaxValue); // Max interation 2147483647
Console.WriteLine(UInt32.MaxValue); // Max interation 4294967295
unsigned int可能是有原因的。