合理布局结构体,精打细算 cacheline
突然想起多年前做的一个优化,相关的还涉及一个诡异的事,先看这两篇文字:保持内存紧凑性,快速路径慎用 kmalloc。
最近又遇到这类事,还是一样的原则,“一起经常被访问的字段要紧挨着放,尽量使它们处于同一个 cacheline”。看一个紧凑型的结构体:
#define CACHELINE_SIZE 128
#define ARRAY_SIZE 100000typedef struct {int a;int b;int c;int d;//char pad[256 - 16];char pad2[519 - 16];
} __attribute__((packed)) CompactStruct;
再看一个非紧凑型的:
#define CACHELINE_SIZE 128
#define ARRAY_SIZE 100000typedef struct {int a;char padding1[CACHELINE_SIZE - sizeof(int) + 1];int b;char padding2[CACHELINE_SIZE - sizeof(int) + 1];int c;char padding3[CACHELINE_SIZE - sizeof(int) + 1];int d;char padding4[CACHELINE_SIZE - sizeof(int) + 1];char pad2[3];
} __attribute__((packed)) ScatteredStruct;
执行下面的逻辑,哪个更快呢:
for (int i = 0; i < ARRAY_SIZE; i++) {sum += arr[i].a + arr[i].b + arr[i].c + arr[i].d;
}
结构体字段的布局也是门手艺。像 TCP 处理,比如在接收路径,会频繁访问 snd_cwnd,snd_una,snd_wnd,snd_nxt,…,那么就有必要让上述这些字段挨得近一些,而不是随意散布在结构体的任意位置。总之,这个优化是立竿见影的。
但这种优化是极度拧巴的体系结构相关,我就从公司的 x86_64 换到家里的 Apple M2,结构体宏定义就改了一大堆,站在程序的视角,它本不应该了解这么多东西,cache 是默默起作用的,但反过来,既然你想让 cache 起更大作用,那就必须理解它的细节,方能调教好它。
但要记住,这类优化措施一定是最后才要考虑的,远在算法优化和系统级优化之后,首先还是要考虑算法能不能更优,压缩大 O 数量级,其次要考虑系统能不能更优,比如锁,并行,再然后就是操作系统级别的,比如进程,线程,最后才是体系结构相关的优化,这也是一个自上而下的顺序,从你要解决问题的算法开始,到硬件结束。
浙江温州皮鞋湿,下雨进水不会胖。