给出以下 C 程序(MSVC 不会为我优化“工作”,对于其他编译器,您可能需要添加
asm
语句):
#include <inttypes.h>
#include <stdlib.h>
#define SIZE 10000
typedef struct {
int32_t a, b, c;
} Struct;
void do_work(Struct* data) {
int32_t* a = malloc(sizeof(int32_t) * SIZE),
* b = malloc(sizeof(int32_t) * SIZE),
* c = malloc(sizeof(int32_t) * SIZE);
int32_t* a_ptr = a, * b_ptr = b, * c_ptr = c;
for (size_t i = 0; i < SIZE; i++, a_ptr++, b_ptr++, c_ptr++, data++) {
*a_ptr = data->a;
*b_ptr = data->b;
*c_ptr = data->c;
}
free(a);
free(b);
free(c);
}
int main() {
Struct* data = malloc(sizeof(Struct) * SIZE);
for (size_t i = 0; i < SIZE; i++) {
data[i].a = i;
data[i].b = i;
data[i].c = i;
}
for (int i = 0; i < 500000; i++) {
do_work(data);
}
free(data);
}
(我在 Rust 中有一个类似的程序,具有相同的结论)。
Intel VTune 报告称,该程序有 63.1% 的内存限制和 52.4% 的存储限制,存储延迟为 26%。它建议搜索虚假共享,但我不明白这里怎么可能有虚假共享。没有并发性,所有数据都由一个核心拥有,访问模式应该很容易预测和预取。我不明白为什么 CPU 需要在这里的商店中停顿。
我认为也许三个分配的地址的低位和高位是相同的,这导致它们被映射到相同的缓存线,但我记得读到现代CPU不只是删除一些位来分配一个缓存行但可以进行更复杂的计算。
另一种想法是,也许在分配被释放后,CPU 仍然忙于刷新存储,并且在下一次运行中,分配器为它们分配了相同的地址(或接近的地址),这给 CPU 带来了问题,因为它已经在存储新数据之前等待。所以我尝试不释放分配,但这导致代码速度慢得多。
我使用的是 Windows 11、笔记本电脑 Intel Core i9-13900HX、32 个逻辑核心、8 个性能核心和 16 个高效核心。
最有可能是
int32_t * SIZE
调用中的 malloc
。如果您使用像 SIZE << 2
这样的位移位,您的代码应该更快、更高效。