【发布时间】:2021-07-14 23:49:57
【问题描述】:
我正在尝试在多核系统上并行化代码。在调查扩展瓶颈时,我最终将所有内容都删除到一个(几乎)空的 for 循环中,并发现在 28 个内核时扩展仍然只有 75%。下面的示例不会导致任何错误共享、堆争用或内存带宽问题。我在许多运行 Linux 或 Mac 的机器上看到了类似或更糟的影响,物理内核数从 8 到 56,所有处理器都处于空闲状态。
该图显示了在专用 HPC Linux 节点上进行的测试。这是一个“弱缩放”测试:工作负载与工作人员的数量成正比,垂直轴显示所有线程组合完成的工作速率,缩放到硬件的理想最大值。每个线程运行 10 亿次空的 for 循环迭代。 1 到 28 之间的每个线程计数有一个试验。每个线程的运行时间约为 2 秒,因此线程创建的开销不是一个因素。
这可能是操作系统妨碍了我们吗?或者可能是功耗?谁能提供一个在高核数机器上显示 100% 扩展的计算示例(无论多么微不足道、弱或强)?
下面是要重现的 C++ 代码:
#include <vector>
#include <thread>
int main()
{
auto work = [] ()
{
auto x = 0.0;
for (auto i = 0; i < 1000000000; ++i)
{
// NOTE: behavior is similar whether or not work is
// performed here (although if no work is done, you
// cannot use an optimized build).
x += std::exp(std::sin(x) + std::cos(x));
}
std::printf("-> %lf\n", x); // make sure the result is used
};
for (auto num_threads = 1; num_threads < 40; ++num_threads)
{
auto handles = std::vector<std::thread>();
for (auto i = 0; i < num_threads; ++i)
{
handles.push_back(std::thread(work));
}
auto t0 = std::chrono::high_resolution_clock::now();
for (auto &handle : handles)
{
handle.join();
}
auto t1 = std::chrono::high_resolution_clock::now();
auto delta = std::chrono::duration<double, std::milli>(t1 - t0);
std::printf("%d %0.2lf\n", num_threads, delta.count());
}
return 0;
}
要运行该示例,请确保编译不带 带优化:g++ -O3 -std=c++17 weak_scaling.cpp。这是重现绘图的 Python 代码(假设您将程序输出通过管道传输到 perf.dat)。
import numpy as np
import matplotlib.pyplot as plt
threads, time = np.loadtxt("perf.dat").T
a = time[0] / 28
plt.axvline(28, c='k', lw=4, alpha=0.2, label='Physical cores (28)')
plt.plot(threads, a * threads / time, 'o', mfc='none')
plt.plot(threads, a * threads / time[0], label='Ideal scaling')
plt.legend()
plt.ylim(0.0, 1.)
plt.xlabel('Number of threads')
plt.ylabel('Rate of work (relative to ideal)')
plt.grid(alpha=0.5)
plt.title('Trivial weak scaling on Intel Xeon E5-2680v4')
plt.show()
更新 -- 这是 56 核节点上的相同扩展,以及该节点的架构:
更新 -- cmets 担心构建未优化。如果在循环中完成工作,结果非常相似,不丢弃结果,使用-O3。
【问题讨论】:
-
有趣。你如何运行程序?此外,您是否使用“默认”系统配置? (即,您是否更改了调控器的配置、超线程、调度算法、频率限制等)。
-
不,我已经在大约六台机器上运行了测试,所有机器都使用默认配置。我没有在示例中包含线程固定(为了简单起见),但核心亲和力并没有改变结果。
-
测试没有优化编译的程序的性能可能没有用,因为当优化被禁用时,程序是故意以这样一种方式构建的,以便调试器(或人类)更容易理解机器代码/汇编级别,而不是快速/高效。因此,它的性能并没有告诉我们太多(如果有的话)关于始终启用优化的“真实世界条件”。
-
这可能与功耗和热环境有关。在几个核心全部运行而其他核心闲置的情况下,处理器有额外的功率和热容量可用,并且可以运行得比额定速度更快(Turbo Boost)。所有核心都用完后,它会减慢到(可能)额定速度,尽管如果它变得太热,它会减慢更多。
-
运行
watch -n.5 'grep "^cpu MHz" /proc/cpuinfo'以查看 CPU 频率如何随着测试的进行而变化。
标签: c++ multithreading performance multiprocessing hpc