Dev*_*inB 48 c++ embedded optimization performance
我正在阅读一个由Introversion游戏编码器撰写的博客文章,他正在忙着试图从代码中挤出每一个CPU蜱.他提到的一个伎俩是
"将类的成员变量重新排序为最常用和最少使用的."
我不熟悉C++,也不熟悉它的编译方式,但我想知道是否
我知道这个技巧节省的(CPU)时间量是最小的,这不是一个交易破坏者.但另一方面,在大多数函数中,很容易识别哪些变量将是最常用的,并且默认情况下只是以这种方式开始编码.
Ste*_*sop 61
这里有两个问题:
它可能有所帮助的原因是内存以称为"缓存行"的块加载到CPU缓存中.这需要时间,一般来说,为对象加载的缓存行越多,所需的时间就越长.此外,更多的其他东西被抛出缓存以腾出空间,这会以不可预测的方式减慢其他代码的速度.
缓存行的大小取决于处理器.如果它与对象的大小相比较大,那么很少有对象将跨越缓存行边界,因此整个优化是无关紧要的.否则,您可能会忘记将对象的一部分放在缓存中,其余部分放在主内存中(或者可能是L2缓存).如果您最常见的操作(访问常用字段的操作)对对象使用尽可能少的缓存,这是一件好事,因此将这些字段组合在一起可以更好地发生这种情况.
一般原则称为"参考地点".程序访问的不同内存地址越接近,获得良好缓存行为的机会就越大.提前预测性能通常很困难:同一架构的不同处理器模型可能表现不同,多线程意味着您通常不知道缓存中会出现什么,等等.但是可以谈论可能发生的事情. , 大多数时候.如果您想知道任何事情,通常需要对其进行测量.
请注意,这里有一些问题.如果您正在使用基于CPU的原子操作(C++ 0x中的原子类型通常会这样),那么您可能会发现CPU锁定整个缓存行以锁定该字段.然后,如果你有几个原子字段靠近在一起,不同的线程在不同的核心上运行并同时在不同的字段上运行,你会发现所有这些原子操作都是序列化的,因为它们都锁定了相同的内存位置,即使它们'重新在不同的领域运作.如果他们在不同的缓存行上运行,那么他们就可以并行运行,并且运行得更快.实际上,正如Glen(通过Herb Sutter)在他的回答中指出的那样,即使没有原子操作,这种情况也会发生,并且可能彻底毁掉你的一天.因此,即使它们共享缓存,参与的位置也不一定是涉及多个核心的好事.你可以期待它,因为缓存未命中通常是速度损失的根源,但在你的特定情况下是非常错误的.
现在,除了区分常用字段和较少使用字段之外,对象越小,它占用的内存越少(因此缓存越少).这是一个非常好的消息,至少在你没有激烈争论的地方.对象的大小取决于其中的字段以及必须在字段之间插入的任何填充,以确保它们正确地对齐体系结构.C++(有时)根据声明的顺序对字段必须出现在对象中的顺序施加约束.这是为了使低级编程更容易.所以,如果你的对象包含:
那么这将占用内存中的16个字节.顺便说一下,int的大小和对齐方式在每个平台上都不一样,但是4很常见,这只是一个例子.
在这种情况下,编译器将在第二个int之前插入3个字节的填充,以正确对齐它,并在末尾插入3个字节的填充.对象的大小必须是其对齐的倍数,以便相同类型的对象可以在内存中相邻放置.这就是所有数组都是C/C++,内存中的相邻对象.如果struct是int,int,char,char,那么同一个对象可能是12个字节,因为char没有对齐要求.
我说int是4对齐是否依赖于平台:在ARM上它绝对必须,因为未对齐访问会引发硬件异常.在x86上,您可以访问未对齐的整数,但它通常较慢且IIRC非原子.所以编译器通常(总是?)在x86上进行4对齐.
编写代码时的经验法则,如果你关心打包,就要看结构的每个成员的对齐要求.然后首先排序最大对齐类型的字段,然后排序下一个最小的字段,依此类推到没有对齐要求的成员.例如,如果我正在尝试编写可移植代码,我可能会想到这个:
struct some_stuff {
double d; // I expect double is 64bit IEEE, it might not be
uint64_t l; // 8 bytes, could be 8-aligned or 4-aligned, I don't know
uint32_t i; // 4 bytes, usually 4-aligned
int32_t j; // same
short s; // usually 2 bytes, could be 2-aligned or unaligned, I don't know
char c[4]; // array 4 chars, 4 bytes big but "never" needs 4-alignment
char d; // 1 byte, any alignment
};
Run Code Online (Sandbox Code Playgroud)
如果您不知道字段的对齐方式,或者您正在编写可移植代码但想要尽可能地做到最好而没有大的诡计,那么您认为对齐要求是结构中任何基本类型的最大要求,并且基本类型的对齐要求是它们的大小.所以,如果你的struct包含uint64_t或long long,那么最好的猜测是它是8-aligned.有时候你会错的,但是你会很多时候都是对的.
请注意,像你的博主一样的游戏程序员经常知道他们的处理器和硬件的一切,因此他们不必猜测.他们知道缓存行大小,他们知道每种类型的大小和对齐方式,并且他们知道编译器使用的结构布局规则(对于POD和非POD类型).如果它们支持多个平台,那么如果需要,它们可以为每个平台提供特殊情况.他们还花了很多时间考虑游戏中的哪些对象将从性能改进中受益,并使用分析器来找出真正的瓶颈所在.但即便如此,无论对象是否需要,您都可以应用一些经验法则并不是一个坏主意.只要它不会使代码不清楚,"将常用字段放在对象的开头"和"按对齐要求排序"是两个很好的规则.