我有一个可疑的编码习惯。
当我需要迭代一小组项目(其计数限制在32000
以下)时,我使用Int16
作为我的i变量类型,而不是Integer
。我这样做是因为我认为使用Int16
比完整的Integer
更高效。
我错了吗?在使用Int16
和Integer
之间是否没有有效的性能差异?我应该停止使用Int16
,并只使用Integer
来满足我所有的计数/迭代需求吗?
在使用数组或集合的索引进行循环时,你几乎总是应该使用Int32
或Int64
(不要使用UInt32
或UInt64
,这样做并不能获得任何好处)。
它效率较低最明显的原因是所有在BCL中找到的数组和集合索引都采用Int32
,所以在试图使用Int16
作为索引的代码中,将始终发生隐式转换。
不太明显的原因(也是数组采用Int32
索引的原因)是CIL规范表明,所有操作栈值都是Int32
或Int64
。每次你将任何其他整数类型(Byte
、SByte
、UInt16
、Int16
、UInt32
或UInt64
)加载或存储到变量中时,都会涉及到一个隐式转换操作。无符号类型在加载时没有惩罚,但在存储值时,这相当于截断和可能的溢出检查。对于有符号类型,每个加载都会扩展符号位,每个存储都会折叠符号位(并进行可能的溢出检查)。
对你最具伤害力的地方是循环本身,而不是数组访问。例如,考虑这个看似无害的循环:
for (short i = 0; i < 32000; i++) {
...
}
看起来很不错,是吗?并不是!你基本上可以忽略初始化(short i = 0
),因为它只会发生一次,但比较(i<32000
)和递增(i++
)部分会发生32000次。以下是在机器级别上查看该内容的伪代码:
Int16 i = 0;
LOOP:
Int32 temp0 = Convert_I16_To_I32(i); // !!!
if (temp0 >= 32000) goto END;
...
Int32 temp1 = Convert_I16_To_I32(i); // !!!
Int32 temp2 = temp1 + 1;
i = Convert_I32_To_I16(temp2); // !!!
goto LOOP;
END:
有3个转换在其中运行了32000次。只需使用Int32
或Int64
就可以完全避免这些转换。
更新:正如我在评论中所说,我现在确实已经写了一篇关于这个主题的博客文章:.NET Integral Data Types And You
Int16可能会比较低效,因为x86字访问的指令占用的空间比双字访问的指令要大。这将取决于JIT所做的事情。但无论如何,在迭代中使用作为变量时,它几乎肯定不会更加高效。
相反的情况是正确的。
32位(或64位)整数比int16更快。通常本地数据类型是最快的。
如果您想使数据结构尽可能精简,则Int16很好。这可以节省空间并提高性能。
对于现代硬件来说,任何性能差异都微乎其微,从实际效果来看没有任何区别。您可以尝试编写几个测试用例并运行它们数百次,取平均循环完成时间,您会明白我的意思。
如果您的资源非常有限-嵌入式系统具有极小的堆栈、针对慢速网络设计的线路协议(例如 GPRS 等),那么从存储角度考虑可能是有意义的。
不要假设效率。
什么更有效率或不是更有效率将因编译器和平台而异。除非您实际测试过,否则无法确定int16或int哪个更有效率。
除非您遇到使用int16可以解决的已证明的性能问题,否则我建议您坚持使用int。
在32位计算机上使用Int32(或者在64位计算机上使用Int64)可以获得最快的性能。如果您真的关心所占用的空间,可以使用更小的整数类型(尽管速度可能会慢一些)。
其他人的观点是正确的,只有在需要极高的存储需求或需要对业务对象字段进行另一级别的强制执行时才使用 Int32(32 位代码)/Int64(64 位代码)以下。当然,在这种情况下仍应该进行适当的属性级别验证。
总的来说,在没有性能问题的情况下不要担心效率。如果出现性能问题,就进行性能分析。如果在性能分析期间两种方式的猜测和检查都无法帮助您解决问题,那么就检查 IL 代码。
不过问得好。你正在学习编译器的工作原理。如果想更有效地学习编程,了解基本的 IL 和 C#/VB 编译器的工作原理是一个很好的主意。
我无法想象使用Int16与int相比会有任何显著的性能提升。
你可以在变量声明中节省一些位。
但当规格发生变化,你所计算的任何东西现在可能超过32767时,这绝对不值得麻烦,因为你会发现当应用程序开始抛出异常时...
使用比Int32更小的数据类型并不能带来明显的性能提升,事实上,我在某处读到,使用Int32将比Int16更快,因为它涉及内存分配。