1

对齐结构/类的数据成员是否真的不再产生以前的好处,尤其是在 nehalem 因为硬件改进?如果是这样,那么对齐总是会带来更好的性能,与过去的 CPU 相比,只是非常小的显着改进吗?

成员变量的对齐是否扩展到成员函数?我相信我曾经读过(它可能在维基书籍“C++ 性能”上)有规则将成员函数“打包”到各种“单元”(即源文件)中以最佳地加载到指令缓存中?(如果我在这里的术语有误,请纠正我)。

4

2 回答 2

4

处理器仍然比 RAM 可以提供的速度快得多,因此它们仍然需要缓存。高速缓存仍然由固定大小的高速缓存行组成。此外,主内存以页的形式交付,并且使用翻译后备缓冲区访问页。同样,此缓冲区具有固定大小的缓存。

这意味着空间和时间位置都很重要(即你如何打包东西,以及你如何访问它)。良好地包装结构(按填充/对齐要求排序)而不是以某种随意的顺序包装它们通常会导致更小的结构尺寸。

较小的结构尺寸意味着,如果您有大量数据:

  • 更多结构适合一个高速缓存行(高速缓存未命中 = 50-200 个周期)
  • 需要更少的页面(页面错误 = 10-20 百万个 CPU 周期)
  • 需要更少的 TLB 条目,更少的 TLB 未命中(TLB 未命中 = 50-500 个周期)

线性处理几 GB 紧密打包的 SoA 数据可能比以糟糕的布局/打包方式以幼稚的方式做同样的事情快 3 个数量级(或 8-10 个数量级,如果涉及页面错误)。

无论您是否将单个4 字节或 2 字节值(例如,典型的intor short)手动对齐到 2 或 4 字节,在最近的 Intel CPU 上都会产生非常小的差异(几乎不明显)。就目前而言,对此进行“优化”似乎很诱人,但我强烈建议不要这样做。
这通常是最好不要担心的事情,并留给编译器来解决。如果没有其他原因,那么因为收益充其量是微不足道的,但是如果您弄错了,其他一些处理器架构会引发异常。因此,如果你试图太聪明,一旦你在其他架构上编译,你就会突然出现无法解释的崩溃。当这种情况发生时,你会感到抱歉。

当然,如果你没有至少几十兆字节的数据要处理,你根本不需要关心。

于 2013-02-18T22:37:20.007 回答
2

对齐数据以适合处理器永远不会受到伤害,但某些处理器会比其他处理器具有更明显的缺点,我认为这是回答这个问题的最佳方法。

将函数对齐到高速缓存行单元对我来说似乎有点红鲱鱼。对于小型函数,如果可能的话,您真正想要的是内联。如果代码不能被内联,那么它可能比缓存行大。[当然,除非它是一个虚函数]。我不认为这曾经是一个巨大的因素 - 要么代码通常被经常调用,因此通常在缓存中,或者它不经常被调用,并且在缓存中不经常被调用。我敢肯定,可能会想出一些代码来调用一个函数,func1() 也会将 func2() 拖到缓存中,所以如果你总是连续调用 func1() 和 func2(),它会有一些好处。但它' s 真的不是什么大好处,除非你有很多函数,它们的函数对或函数组被紧密地调用在一起。[顺便说一句,我不认为编译器可以保证以任何特定的顺序放置你的函数代码,无论你把它放在源文件中的哪个顺序]。

缓存对齐是一个稍微不同的问题,因为如果你把它做对了,而不是把它弄错了,缓存行仍然会产生巨大的影响。这对于多线程来说比一般的“加载数据”更重要。这里的关键是避免在处理器之间共享同一高速缓存行中的数据。在我大约 10 年前从事的一个项目中,一个基准测试有一个函数,它使用一个由两个整数组成的数组来计算每个线程执行的迭代次数。当它被分成两个独立的缓存线时,基准测试从在单个处理器上运行的 0.6 倍提高到在一个处理器上运行的 1.98 倍。在现代 CPU 上也会发生同样的效果,即使它们速度更快 - 效果可能不完全相同,但速度会大大降低(并且共享数据的处理器越多,效果就越大,所以四核系统会比双核更差,等等)。这是因为每次处理器更新缓存行中的某些内容时,所有其他已读取该缓存行的处理器都必须从更新它的处理器[或过去从内存中]重新加载它。

于 2013-02-18T22:37:10.783 回答