我正在将一个现有应用程序移植到C#,并希望尽可能提高性能。许多现有的循环计数器和数组引用是定义为System.UInt32,而不是我本来会使用的Int32。
使用UInt32与Int32相比是否存在显着的性能差异?
我正在将一个现有应用程序移植到C#,并希望尽可能提高性能。许多现有的循环计数器和数组引用是定义为System.UInt32,而不是我本来会使用的Int32。
使用UInt32与Int32相比是否存在显着的性能差异?
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;
}
}
Signed: 00:00:00.5066966
Unsigned: 00:00:00.5052279
作为一条常见的指令,可以安全地假定大多数现代高功率处理器都将拥有操作的硬件指令,并且它们很可能在相同数量的周期内执行,但这并不是保证。低功率处理器可能具有较少的指令,并且没有无符号整数的分支。在这种情况下,JIT编译器可能需要发出多个硬件指令(例如首先进行转换,然后进行分支)来执行blt.un.s IL指令。即使是这种情况,这些附加指令也将是基本的,可能不会对性能产生显著影响。blt.un.s : 短格式,如果小于(无符号或无序),则跳转至目标。
blt.s : 短格式,如果小于,则跳转至目标。
这里有两件事情需要考虑:首先,无符号整数不符合CLS-compliant标准,这意味着如果您将无符号整数作为API的一部分暴露给另一个程序(例如如果您正在分发可重用库),则可能会遇到问题。其次,.NET中的大多数操作(包括BCL公开的方法签名)都使用带符号整数(出于上述原因)。因此,如果您计划实际使用无符号整数,则可能会发现自己经常进行强制转换。这会对性能造成非常小的影响,并使您的代码有点混乱。最终,这可能并不值得。
TLDR; 回顾我在C ++时代的话,“使用最恰当的东西,让编译器解决其余的问题。” C#并不是那么简单明了,所以我会说这个建议适用于.NET:在x86 / x64上,带符号和无符号整数之间实际上没有性能差异,但大多数操作需要带符号整数,因此除非您真正需要限制仅为正值,或者您确实需要符号位占用的额外范围,否则请坚持使用带符号整数。最终,您的代码将更加简洁。
我认为没有什么性能上的考虑,除了处理器级别上可能存在的有符号和无符号算术差异,但此时我认为这些差异是无关紧要的。
更大的差异在于CLS兼容性,因为某些语言不支持无符号类型,所以无符号类型不符合CLS标准。
我没有在.NET方面进行任何研究,但在Win32/C++的旧日子里,如果您想将“signed int”转换为“signed long”,CPU必须运行一个操作来扩展符号。要将“unsigned int”转换为“unsigned long”,只需在上位字节中填充零即可。这样可以节省几个时钟周期(即,您必须执行数十亿次才能产生明显的差异)。
就性能而言,没有区别。简单的整数计算是众所周知的,现代CPU已经高度优化以快速执行它们。
这些类型的优化很少值得努力。使用最适合任务的数据类型,然后就不要再管它了。如果这个东西碰到数据库,你可能会发现在数据库设计、查询语法或索引策略方面进行一打调整就能够抵消在 C# 代码优化方面所取得的成果。
无论哪种方式,它都将分配相同数量的内存(尽管其中一种可以存储更大的值,因为它不会为符号保存空间)。因此,除非您使用会导致其中一种选项爆炸的大值/负值,否则我怀疑您是否会看到“性能”差异。
int
的原因,例如for(int i=0;i<bla;i++)
。而且,我经常想使用unsigned
来避免检查范围。不幸的是(无论是在C++还是在C#中出于类似的原因),建议不要使用unsigned
来获得一个比特或确保非负性,如下所述:
这段话摘自“C++程序设计语言”第73页,作者是该语言的创造者Bjarne Stroustrup。"使用
unsigned
代替int
来获得一个比特以表示正整数几乎从来不是一个好主意。通过声明变量unsigned
来确保某些值为正数的尝试通常会被隐式转换规则所打败"
integer
类型的偏见。assert
的integer
与unsigned
进行相同的练习将是有趣的。这与性能无关,而是与循环计数器的要求有关。
也许需要完成许多迭代。
Console.WriteLine(Int32.MaxValue); // Max interation 2147483647
Console.WriteLine(UInt32.MaxValue); // Max interation 4294967295