假设我们有一个运行100次的循环。使用unsigned char
代替int
作为其计数器有何不同?还使用i += 1U
代替i++
?或者编译器会处理这个问题吗?
答案 0 :(得分:7)
简单来说,int
vs unsigned char
为我提供相同的代码:
for ( unsigned char i = 0; i < 100; ++i ) {
01081001 mov esi,64h
01081006 jmp main+10h (1081010h)
01081008 lea esp,[esp]
0108100F nop
std::cout << 1 << std::endl;
01081010 mov eax,dword ptr [__imp_std::endl (108204Ch)]
01081015 mov ecx,dword ptr [__imp_std::cout (1082050h)]
0108101B push eax
0108101C push 1
0108101E call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1082044h)]
01081024 mov ecx,eax
01081026 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1082048h)]
0108102C dec esi
0108102D jne main+10h (1081010h)
}
return 0;
您应该对代码进行分析,然后优化慢速部分。 不要以premature optimization开头。
答案 1 :(得分:3)
我建议使用++ i而不是i + = 1或i ++
答案 2 :(得分:3)
使用i += 1U
将没有任何区别。编译器会将其视为与i++
完全相同。 int
与unsigned char
的情况比较复杂。
最佳答案根本不用担心它,除非a)你的代码必须运行得更快并且b)你已经将循环识别为主要瓶颈。记住Knuth定律:过早优化是万恶之源。
下一个最佳答案是编译器可能会优化任何差异。即使它不会,100次重复是花生。循环索引操作完全无关紧要。
但是,为了“完全披露”,你的int / uchar问题的答案是,几乎可以肯定没有性能差异。几乎。许多相关因素未被C标准指定,因此理论上可能存在一个C环境,其中uchar会更快。
标准将char定义为最小的可寻址内存单元。这可能(几乎总是)将是8位的一个字节。 int将至少与char一样大(或者char显然不能是内存的最小可寻址单位,并且它必须能够保存-32,767到+32,767之间的任何值。(它不是不是需要保留-32,768,尽管几乎所有都这样做。)这意味着int必须至少为16位。实际上,int通常是一个机器字。这样做的好处是速度快。在你的机器上而我的,几乎肯定是32或64位。
现在,假设我们在一台8位字的机器上。假设16位整数没有特殊的硬件支持。 (这可能与也可能不对应于任何实际的机器。)char将是8位且int可能是16。对int的操作可能很昂贵,因此char可能更快。
假设我们有32位字和32位整数。假设您的代码包含四个(8位)char变量。进一步假设您的编译器在物理上尽可能多地打包局部变量,速度会被诅咒。如果其中一个字符是您的循环计数器,它可能与其他变量包装在一个单词中。对该字符的每个操作都可能是昂贵的,因为它必须重新包装。 (这假定编译器相当笨。)然而,int计数器无法与任何其他变量打包在一起。在这种情况下,int可能运行得稍快。
请记住,这两个例子都是人为的。我不知道任何真正的系统可以使用这些方式,但系统可以。 (注意:如果我提出的案例与规格不符,请告诉我,以便我能纠正。)
那么,在现实世界中,如何决定使用哪种类型?来自C Language FAQ:
如果您可能需要较大的值(大于32,767或小于-32,767),请使用long。否则,如果空间非常重要(即,如果有大型阵列或许多结构),请使用short。否则,使用int。如果明确定义的溢出特性很重要而负值不重要,或者如果要在操作位或字节时避免出现符号扩展问题,请使用相应的无符号类型之一。 (但要注意在表达式中混合有符号和无符号值时;请参阅问题3.19。)
虽然字符类型(特别是unsigned char)可以用作“小”整数,但这样做有时会比它的价值更麻烦。编译器必须发出额外的代码以在char和int之间进行转换(使可执行文件更大),并且意外的符号扩展可能很麻烦。 (使用unsigned char可以提供帮助;有关相关问题,请参阅问题12.1。)
该页面的其余部分,以及整个常见问题解答,非常值得一读。
在这种情况下?使用int并且不用担心。
答案 3 :(得分:2)
这通常没有任何区别。如果你真的在乎,请尝试并测量,但首先要确保这是一个瓶颈。
答案 4 :(得分:2)
说真的,将这些微优化留给编译器。即使是的差异,只有你每秒钟进行数万次才会变得明显。对于绝大多数情况来说,一点都不重要,因为大多数程序花费99.9%的时间等待一些事情发生。
如果您专注于算法选择等宏观内容,您也更有可能获得更高的投资回报。
答案 5 :(得分:1)
据我所知,性能数据从编译器变为编译器。除此之外,性能可能因操作系统而异。
更好的方法是编写一个小程序来衡量您感兴趣的操作的性能。
Programming Pearls 涵盖了有关原始操作性能的大量细节。它还展示了如何编写一些程序来衡量这些操作的性能。
答案 6 :(得分:1)
如果您关心像这样的微优化,您必须全心全意地了解您的编译器。尝试两种方式并比较生成的程序集。
一个好的经验法则是根据其语义选择变量类型(char
用于ASCII字符,int
用于整数,等等......)而不是其大小(除了对齐)你的结构)。
但有两件小事:
在32位架构上,处理器注册表的大小为32位,因此使用访问char
(8位)需要两次操作,而访问int
只需要一次。
如果您不打算在增量之前使用该值,请选择预增量到后增量。这在C ++中尤其重要,其中后增量可能会导致一些不需要的构造函数调用。
答案 7 :(得分:1)
由于差异(最多)是每个增量的一个时钟周期的一小部分,因此在开始接近人类感知的阈值之前,你需要在整个循环中运行1,000,000;假设运行循环1,000,000次,然后立即警告用户已完成循环。
假设循环任何其他;或者,单个通知是任何形式的IO(根据定义它),你必须运行(100万次迭代)外循环另外1000万次!在您的用户注意之前。
认真!你担心错误的事情。
答案 8 :(得分:0)
大多数编译器会识别出您正在将该变量用于循环,因此会将其保存在寄存器中。
在i386 CPU上,它可以使用8位寄存器,因为它们可以直接访问而不会降低性能(16位值只能通过“16位覆盖操作码”访问,导致代码段中再多一个字节)
然而,8位寄存器和32位寄存器一样快,所以使用8位寄存器的唯一好处是还有一个用于另一个8位变量(只有一个,因为更高的16位不能直接访问) )。
总结一下:让编译器进行优化 - 如果可能的话它会这样做,但在这种情况下它也几乎没有差别。
其他CPU(PowerPC,ARM等)还有其他行为。