Answers:
混合int(任何类型)和float的巨大代价是因为它们位于不同的寄存器集中。要从一个寄存器集转到另一个寄存器,您必须将该值写到内存中并读回,这会导致加载命中存储停顿。
在不同大小或整数的正负号之间移动会使所有内容保持在同一寄存器集中,因此避免了大笔费用。由于符号扩展等原因,可能会有较小的罚款,但这些罚款要比加载命中商店小得多。
我怀疑有关Xbox 360和PS3的信息将专门用于仅允许开发人员使用的墙壁,例如大多数低级详细信息。但是,我们可以构造一个等效的x86程序并将其反汇编以得到一个总体思路。
首先,让我们看看未签名的扩展费用:
unsigned char x = 1;
unsigned int y = 1;
unsigned int z;
z = x;
z = y;
相关部分分解为(使用GCC 4.4.5):
z = x;
27: 0f b6 45 ff movzbl -0x1(%ebp),%eax
2b: 89 45 f4 mov %eax,-0xc(%ebp)
z = y;
2e: 8b 45 f8 mov -0x8(%ebp),%eax
31: 89 45 f4 mov %eax,-0xc(%ebp)
因此基本上相同-在一种情况下,我们移动一个字节,在另一种情况下,我们移动一个字。下一个:
signed char x = 1;
signed int y = 1;
signed int z;
z = x;
z = y;
变成:
z = x;
11: 0f be 45 ff movsbl -0x1(%ebp),%eax
15: 89 45 f4 mov %eax,-0xc(%ebp)
z = y;
18: 8b 45 f8 mov -0x8(%ebp),%eax
1b: 89 45 f4 mov %eax,-0xc(%ebp)
因此,符号扩展的成本是子指令级别movsbl
而不是成本movzbl
。由于现代处理器的工作方式,这基本上不可能在现代处理器上进行量化。从内存速度到缓存再到流水线之前的一切,其他一切都将主导运行时。
在大约10分钟的时间里,我编写了这些测试,我很容易找到了一个真正的性能错误,而且,只要打开任何级别的编译器优化,对于这些简单的任务,代码就变得无法识别。
这不是堆栈溢出,所以我希望这里没有人会声称微优化无关紧要。游戏通常会处理非常大且非常数字化的数据,因此,对分支,强制转换,调度,结构对齐等方面的注意会带来非常关键的改进。任何花了很多时间来优化PPC代码的人都可能至少有一个关于负载打击商店的恐怖故事。但是在这种情况下,这真的没有关系。整数类型的存储大小只要对齐并适合寄存器,就不会影响性能。
在几乎所有架构上,带符号整数运算的开销都可能更高。例如,用无符号表示的常量除法更快,例如:
unsigned foo(unsigned a) { return a / 1024U; }
将被优化为:
unsigned foo(unsigned a) { return a >> 10; }
但...
int foo(int a) { return a / 1024; }
将优化为:
int foo(int a) {
return (a + 1023 * (a < 0)) >> 10;
}
或在分支价格便宜的系统上,
int foo(int a) {
if (a >= 0) return a >> 10;
else return (a + 1023) >> 10;
}
模也一样。非2的幂次也是如此(但示例更复杂)。如果您的体系结构没有硬件划分(例如,大多数ARM),则非const的无符号划分也将更快。
通常,告诉编译器不会产生负数将有助于表达式的优化,尤其是用于循环终止和其他条件的表达式。
至于不同的int大小,是的,这会产生轻微的影响,但是您必须权衡一下,而不是移动较少的内存。如今,访问较少的内存可能比增加大小所损失的更多。那时,您还需要进行微优化。
乔恩·普迪(Jon Purdy)上面说(我无法评论),未签名可能会更慢,因为它不会溢出。我不同意,无符号算术是对单词中位数进行模2的简单模运算。原则上,已签名的操作可能会发生溢出,但是通常会将其关闭。
有时,您可以做一些聪明的(但不是很容易理解的事情),例如将两个或多个数据项包装到一个int中,并且每条指令获得多个操作(口袋算术)。但是您必须了解自己在做什么。当然,MMX可以让您自然地做到这一点。但是有时使用最大的硬件支持的字大小并手动打包数据可为您提供最快的实现。
注意数据对齐。在大多数硬件实现中,未对齐的加载和存储速度较慢。自然对齐意味着对于一个4字节的字,地址是4的倍数,而8字节的字地址应该是8字节的倍数。这会延续到SSE(128位有利于16字节对齐)。AVX很快将把这些“向量”寄存器的大小扩展到256位,然后是512位。并且对齐的加载/存储将比未对齐的加载/存储更快。对于硬件极客,未对齐的内存操作可能会跨越诸如高速缓存行甚至页面边界之类的事情,硬件必须对此加以注意。
正如celion所指出的,在int和float之间转换的开销主要与寄存器之间值的复制和转换有关。无符号int及其自身的唯一开销来自其有保证的环绕行为,这需要在编译后的代码中进行一定量的溢出检查。
在有符号和无符号整数之间进行转换基本上没有开销。根据平台的不同,整数的大小可能(无限地)更快或更慢。一般而言,最接近平台字大小的整数大小将是访问速度最快的,但是整体性能差异取决于许多其他因素,尤其是缓存大小:如果uint64_t
在需要时使用uint32_t
,则可能因为较少的数据会立即放入缓存中,并且可能会产生一些负载开销。
但是,甚至考虑这个问题也有些过分。如果您使用适合于数据的类型,则应该可以很好地工作,并且通过基于体系结构选择类型所获得的功能可以忽略不计。