Skip to content

About

研一下计算系统跨层优化实验

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

建议直接读文章:实验-CPU 核心数增加但程序更慢

其他详细的信息见文件:运行入口.md

研究问题

问题1 为什么 CPU 线程数越多,但是程序不一定越快?

原因:增加线程可以把计算分摊出去,但线程之间的相互干扰也可能增加,导致整体时间不一定更快。线程的相互干扰是什么下面有提到。

用计数器模拟总任务。计数器累计到 12 亿次,任务结束。

问题2 相同的多线程下,不同的内存布局,会如何影响多线程性能?

不同的内存布局是指计数器存放位置:是否在一个缓存行。

实验方案

实验一

目的:验证问题 1

在 Global(下面提到的 3 种计数器更新方式的一种)条件下,统计用 6 档线程数(1、2、4、8、16、32)完成全部任务的耗时,以及相对单线程的加速比。

实验二

目的:验证问题 2

在相同的线程数量(1、2、4、8、16、32)下,分别在Compact、Padded 条件下,统计任务耗时。

总工作量为 12 亿次,每种配置重复 10 次,用中位数比较。项目同时保留了全部原始数据。

计数器三种更新方式

Global

多个线程共同修改一个全局变量。

可能的硬件开销:线程 A 修改计数器时,需要获得写入权限,权限的传递需要代价;A 修改了计数器,B 缓存的有计数器的副本,硬件需要让 B 的副本失效,需要代价;

Compact

没有全局计数器。线程有自己的计数器,多个线程的计数器之和等于 12 亿,任务完成。

和 Padded 区别:计数器在内存中连续存放,每组最多 8 个计数器位于同一个 64 B 缓存行;16、32 线程分别使用 2、4 条缓存行。

CPU 按缓存行维护缓存一致性,因此各线程虽然写的是自己的计数器,但是仍可能互相干扰。

对缓存行的竞争,带来的开销和 Global 中修改全局变量类似。

怎么保证计数器在放在一个缓存行?

服务器的缓存行大小 64 字节。计数器大小 8 字节。

起点对齐:alignas(64) 保证每组的起始地址是 64 的整数倍:缓存行边界。

组内连续:CompactLine 内部的原子计数器数组包含 8 个连续元素,外部 vector<CompactLine> 连续存放这些对齐的组;组内 8 个计数器连续存放,起始偏移分别为 0、8、16、24、32、40、48、56 字节,恰好占满这一行。

Padded

没有全局计数器。线程有自己的计数器,所有工作线程的计数器之和等于 12 亿,任务完成。

和 Compact 区别:各计数器在内存中隔开存放(让每个计数器占据一个对齐的 64 字节区域,确保不同计数器不在同一个缓存行)。和 Compact 相比,减少了缓存行竞争带来的开销。

怎么保证计数器不在同一个缓存行?

把每个计数器包装成一个大小为 64 字节、起始地址按 64 字节对齐的对象。

实验结果

全部 180 条原始测量见 results/raw_results.csv,18 种配置的统计汇总见 results/summary.csv。每种配置有 10 次测量,未剔除较慢的样本。

这里简单介绍一些实验结果。

第一个问题:为什么线程更多,程序不一定更快?

Global 耗时与加速比

Global 实验数据如下:

工作线程数 耗时中位数
1 1.591 秒
2 4.127 秒
4 4.289 秒
8 4.380 秒
16 6.317 秒
32 6.437 秒

所有配置都处理同样的 12 亿次总任务,但 32 线程耗时约为单线程的 4.05 倍。这直接说明:更多工作线程并不保证更高性能。

计算可以分配到多个核心,但对这个共享位置的更新仍需要协调;结果与“核心之间的竞争和缓存一致性开销抵消了并行收益”的解释一致,但本次没有直接用硬件性能计数器分解各项开销。

第二个问题:相同线程数,不同内存布局会如何影响性能?

Compact 与 Padded 布局对照

工作线程数 Compact 中位数 Padded 中位数 Padded 加速倍数
1 1866.89 ms 1878.10 ms 0.99 倍
2 3596.31 ms 939.24 ms 3.83 倍
4 4624.14 ms 473.76 ms 9.76 倍
8 4216.73 ms 239.05 ms 17.64 倍
16 2106.15 ms 120.86 ms 17.43 倍
32 1085.39 ms 68.26 ms 15.90 倍

单线程时几乎没有差别:没有多个核心相互干扰,隔开计数器没有明显收益。

多线程时差距明显:Compact 每 8 个计数器共享一条缓存行;Padded 每个计数器独占一行。隔离布局显著改善了性能。

实验服务器参数

项目 实测配置
CPU AMD Ryzen Threadripper PRO 7975WX 32-Cores
架构 x86_64
CPU 插槽 / 物理核心 / 逻辑线程 1 / 32 / 64;每核 2 个 SMT 线程
在线及当前进程可用 CPU 0–63;nproc = 64,进程亲和性为 0–63
NUMA 1 个节点,node 0 包含 CPU 0–63
缓存行大小 64 B;sysfs 中各级缓存均为 64 B
L1 数据缓存 每核 32 KiB,共 32 个实例,总计 1 MiB
L1 指令缓存 每核 32 KiB,共 32 个实例,总计 1 MiB
L2 缓存 每核 1 MiB,共 32 个实例,总计 32 MiB
L3 缓存 每组 32 MiB,共 4 个实例,总计 128 MiB;每组由 8 个物理核心共享
内存 系统可见约 251.2 GiB(MemTotal: 263374732 kB)
操作系统 Ubuntu 24.04 LTS
内核 Linux 7.0.0-38-generic,x86_64
CPU 调频 CPU 0 的 governor 为 powersave;

CPU 0–31 对应 32 个不同物理核心;CPU 32–63 是其 SMT 同胞。CPU i 与 CPU i+32 共享同一物理核心(i 为 0–31)。

本次运行使用 CPU 0–31,每个物理核心绑定一个工作线程;1–8 线程处于同一 L3 组,16、32 线程跨 2、4 组。四组 L3 的逻辑 CPU 范围依次为 0–7/32–39、8–15/40–47、16–23/48–55、24–31/56–63。boost 开启、频率未固定,绑核不代表独占核心。

About

研一下计算系统跨层优化实验

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages