MST
星途 面试题库

面试题:C++对象成员变量布局对性能的影响

C++对象的成员变量布局会影响内存访问效率,进而影响性能。请详细说明编译器如何对成员变量进行布局,以及不同布局策略对缓存命中率的影响。如果有一个包含多个不同类型成员变量的类,如何设计成员变量顺序以提高性能?
40.1万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

编译器对成员变量的布局方式

  1. 基本规则
    • 编译器按照成员变量声明的顺序依次分配内存空间。但是,为了满足数据对齐的要求,可能会在成员变量之间插入填充字节。数据对齐是为了提高内存访问效率,现代计算机系统通常要求特定类型的数据在内存中按照特定的边界对齐。例如,在32位系统中,4字节的int类型变量通常要求其内存地址是4的倍数;在64位系统中,8字节的double类型变量通常要求其内存地址是8的倍数。
    • 对于类中的基类子对象,其布局在派生类对象的开头,遵循基类自身的布局规则。
    • 虚函数表指针(如果类有虚函数)通常位于对象的开头部分。
  2. 示例说明
    • 假设有如下类定义:
class Example {
    char a;
    int b;
    short c;
};
  • 假设char占1字节,int占4字节,short占2字节。由于int类型需要4字节对齐,编译器会在char类型的a后面插入3个填充字节,使得b的地址是4的倍数。short类型的c不需要额外填充,因为它可以从b之后自然对齐。这样,Example类对象的总大小为1 + 3 + 4 + 2 = 10字节。

不同布局策略对缓存命中率的影响

  1. 缓存原理
    • 计算机的缓存是为了缓解CPU和内存之间的速度差异。缓存以缓存行(cache line)为单位进行数据传输,通常缓存行大小在32字节到128字节之间。当CPU访问内存中的数据时,会将包含该数据的整个缓存行加载到缓存中。如果后续访问的数据也在这个缓存行中,就可以直接从缓存中获取,这就是缓存命中。
  2. 布局影响
    • 紧凑布局:如果成员变量布局紧凑,使得相关的成员变量尽可能地靠近,它们更有可能被加载到同一个缓存行中。例如,在一个表示向量的类中,如果xyz坐标成员变量紧密排列,当访问其中一个坐标时,其他坐标也更可能已经在缓存中,从而提高缓存命中率。
    • 非紧凑布局:如果成员变量布局不合理,例如频繁访问的成员变量之间间隔较大,可能会导致每次访问都需要从内存中加载新的缓存行,降低缓存命中率。比如,一个游戏对象类中,经常访问的位置信息和很少访问的纹理数据放在一起,当访问位置信息时,纹理数据也会被加载到缓存中,占用缓存空间,而纹理数据又很少被用到,降低了缓存空间的有效利用率。

包含多个不同类型成员变量的类设计成员变量顺序以提高性能的方法

  1. 按访问频率分组
    • 将经常一起访问的成员变量放在相邻位置。例如,在一个图形渲染类中,如果经常同时访问顶点坐标和颜色信息,应将表示顶点坐标和颜色的成员变量紧挨着声明。
  2. 按数据类型大小排序
    • 一般来说,先声明较大的数据类型成员变量,再声明较小的数据类型成员变量。这样可以减少填充字节的数量,使得对象布局更加紧凑。例如,先声明double类型变量,再声明int类型变量,最后声明char类型变量。
  3. 避免跨越缓存行
    • 了解缓存行的大小(例如64字节),尽量将频繁访问的成员变量安排在不超过一个缓存行的范围内。如果一个类的成员变量较多,可以根据访问频率和相关性,将其分成多个组,每个组的大小尽量控制在缓存行大小以内。
  4. 考虑虚函数表指针
    • 如果类有虚函数,虚函数表指针通常在对象开头。在安排成员变量顺序时,要考虑虚函数表指针对数据对齐和缓存布局的影响。例如,尽量将经常访问的成员变量放在离虚函数表指针较远的位置,避免虚函数表指针的更新频繁干扰到成员变量的缓存命中。