我正在尝试测试缓存行一致性规则对性能的影响。测试代码如下:
constexpr uint64_t loop = 1000000000;
struct no_padding_struct {
no_padding_struct() :x(0), y(0) {}
uint64_t x;
uint64_t y;
};
struct padding_struct {
padding_struct() :x(0), y(0) {}
uint64_t x;
char padding[64];
uint64_t y;
};
alignas(64) volatile no_padding_struct n;
alignas(64) volatile padding_struct p;
constexpr core_a = 0;
constexpr core_b = 1;
void func(volatile uint64_t* addr, uint64_t b, uint64_t mask) {
SetThreadAffinityMask(GetCurrentThread(), mask);
for (uint64_t i = 0; i < loop; ++i) {
*addr += b;
}
}
void test1(uint64_t a, uint64_t b) {
thread t1{ func, &n.x, a, 1<<core_a };
thread t2{ func, &n.y, b, 1<<core_b };
t1.join();
t2.join();
}
void test2(uint64_t a, uint64_t b) {
thread t1{ func, &p.x, a, 1<<core_a };
thread t2{ func, &p.y, b, 1<<core_b };
t1.join();
t2.join();
}
int main() {
uint64_t a, b;
cin >> a >> b;
auto start = std::chrono::system_clock::now();
//test1(a, b);
//test2(a, b);
auto end = std::chrono::system_clock::now();
cout << (end - start).count();
}
结果主要如下:
x86 x64
cores test1 test2 cores test1 test2
debug release debug release debug release debug release
0-0 4.0s 2.8s 4.0s 2.8s 0-0 2.8s 2.8s 2.8s 2.8s
0-1 5.6s 6.1s 3.0s 1.5s 0-1 4.2s 7.8s 2.1s 1.5s
0-2 6.2s 1.8s 2.0s 1.4s 0-2 3.5s 2.0s 1.4s 1.4s
0-3 6.2s 1.8s 2.0s 1.4s 0-3 3.5s 2.0s 1.4s 1.4s
0-5 6.5s 1.8s 2.0s 1.4s 0-5 3.5s 2.0s 1.4s 1.4s
我的CPU是intel core i7-9750h
。 “ core0”和“ core1”属于物理核心,“ core2”和“ core3”等也是如此。 MSVC 14.24被用作编译器。
记录的时间是几次运行中最佳成绩的近似值,因为有大量的后台任务。我认为这很公平,因为可以将结果清楚地分为几组,而0.1s〜0.3s的误差不会影响这种划分。
Test2很容易解释。由于x
和y
位于不同的缓存行中,因此,在2个物理内核上运行可以提高2倍的性能提升(此处可忽略在单个内核上运行2个线程时上下文切换的成本),并且可以在一个内核上运行SMT的效率比2个物理内核低,受制于咖啡杯的吞吐量限制(相信Ryzen可以做得更好),并且比临时多线程效率更高。看来这里的64位模式更有效。
但是test1的结果令我感到困惑。首先,在调试模式下,0-2、0-3和0-5比0-0慢,这是有道理的。我之所以这样解释,是因为某些数据是从L1到L3以及从L3到L1反复移动的,因为缓存必须保持2个内核之间的一致性,而当在单个内核上运行时,它始终保持在L1中。但是,该理论与0-1对始终是最慢的事实相矛盾。从技术上讲,两个线程应该共享相同的L1缓存。 0-1的运行速度应是0-0的2倍。
其次,在释放模式下,0-2、0-3和0-5比0-0快,这证明了上述理论。
Last,0-1在64位和32位模式下的运行速度都比release
中的运行慢。那是我最不明白的。我阅读了生成的汇编代码,没有发现任何帮助。
这可以通过超线程来解释。共享为2个超线程内核的内核不会像2个完全独立的内核那样获得两倍的吞吐量。相反,您可能会得到大约1.7倍的性能。
确实,您的处理器具有6个核心和12个线程,如果正确读取所有这些信息,那么core0 / core1是同一基础核心上的2个线程。
实际上,如果您在脑海中想象到超线程是如何工作的,并且将两个独立的内核的工作交织在一起,也就不足为奇了。