类成员的顺序可能影响性能,但通常不会由于偏移。因为如上所述,几乎所有架构都有带偏移量的加载/存储。对于小型结构,您需要在 x86 上多出 1 个字节,在固定宽度的 ISA 上多出 0 个字节(但即使有了额外的字节,x86 指令通常仍然比那些 ISA 中的固定 4 字节指令短)。如果结构很大,那么 x86-64 中的 4 字节位移可能还需要 4 个字节,但指令计数仍然是 1。在固定宽度的 ISA 上,您至少需要另一条指令才能获得 32 位位移,但是与缓存未命中的影响相比,偏移计算成本只是很小的,这是更改成员位置时可能导致性能下降的主要因素。
因此类成员的顺序会影响字段在缓存中的位置,并且您会希望重要成员位于缓存中并位于同一缓存行中。通常,您会将最大的热成员放在开头以避免填充。但是,如果最热门的成员很小,那么如果它们不会导致填充,则将它们移到开头可能会更好。例如
struct mystruct
{
uint32_t extremely_hot;
uint8_t very_hot[4];
void* ptr;
}
如果 ptr 不经常被访问,最好在这样的热门字段之后保留它
但移动字段并不总是更好的解决方案。在许多情况下,您可能会考虑将班级分成两份,一份用于热成员,另一份用于冷成员。事实上,我在某处读到英特尔编译器有一个特性,当运行配置文件引导优化时,它会自动将类的热成员和冷成员拆分为单独的类。可惜我现在找不到源
举个简单的例子
struct game_player
{
int32_t id;
int16_t positionX;
int16_t positionY;
int16_t health;
int16_t attribute;
game_player* spouse;
time_t join_time;
};
game_player[MAX_PLAYERS];
在屏幕上渲染对象时,只有前5个字段是常用的,因此我们可以将它们拆分为一个热门类
struct game_player_hot
{
int32_t id;
int16_t positionX;
int16_t positionY;
int16_t health;
int16_t attribute;
};
struct game_player_cold
{
game_player* spouse;
time_t join_time;
};
game_player_hot players_hot[MAX_PLAYERS];
game_player_cold players_cold[MAX_PLAYERS];
如果同时或以相同方式访问不同对象的相同字段,有时建议使用 SoA(数组结构)而不是 AoS(结构数组)或混合使用。例如,如果我们有一个向量列表来求和,而不是
struct my_vector
{
uint16_t x, y, z;
uint16_t attribute; // very rarely used
}
my_vector vectors[MAX];
我们将使用
struct my_vector
{
uint16_t x[MAX]; // hot
uint16_t y[MAX]; // hot
uint16_t z[MAX]; // hot
uint16_t attribute[MAX];
}
这样,所有维度值都保持热度并彼此靠近。现在我们也有了更简单更好的矢量化,而且还能让热点保持热度。
更多信息请阅读