c++ - 为什么 N 个独立计算在 N 个线程上没有快 N 倍?

标签 c++ multithreading multicore cpu-cores parallelism-amdahl

我有一个 N 核处理器(在我的例子中是 4 个)。为什么 N 个线程上的 N 个完全独立的函数调用不是快 N 倍左右(当然创建线程会产生开销,但请进一步阅读)?

看下面的代码:

namespace ch = std::chrono;
namespace mp = boost::multiprecision;
constexpr static unsigned long long int num = 3555;

// mp_factorial uses boost/multiprecision/cpp_int, so I get legit results

    ch::steady_clock::time_point s1 = ch::steady_clock::now();
    auto fu1 = std::async(std::launch::async, mp_factorial, num);
    auto fu2 = std::async(std::launch::async, mp_factorial, num);
    auto fu3 = std::async(std::launch::async, mp_factorial, num);
    auto fu4 = std::async(std::launch::async, mp_factorial, num);
    fu1.get(); fu2.get(); fu3.get(); fu4.get();
    ch::steady_clock::time_point e1 = ch::steady_clock::now();

    ch::steady_clock::time_point s2 = ch::steady_clock::now();
    mp_factorial(num);
    mp_factorial(num);
    mp_factorial(num);
    mp_factorial(num);
    ch::steady_clock::time_point e2 = ch::steady_clock::now();

    auto t1 = ch::duration_cast<ch::microseconds>(e1 - s1).count();
    auto t2 = ch::duration_cast<ch::microseconds>(e2 - s2).count();

    cout << t1 << " " << t2 << endl;

我得到的结果如下:

11756 20317

大约快 2 倍。我也尝试过使用大量数字,例如 num = 355555。我得到了非常相似的结果:

177462588 346575062

为什么会这样?我完全了解阿姆达尔定律,多核处理器并不总是快 number_of_cores 倍,但是当我有 < strong>独立 操作,我期待更好的结果。至少接近 number_of_cores


更新:

如您所见,所有线程都按预期工作,所以这不是问题所在:

Workload of threads

最佳答案

这里的问题是你肯定有一些大的 block 状数字,它们不适合你的处理器的 L1 和 L2 缓存,这意味着处理器坐着并转动它的小 ALU 手指,而内存 Controller 在整个过程中跳跃尝试为每个处理器读取一点内存。

当您在一个线程中运行时,该线程至少在很大程度上只在三个不同的内存区域上工作(a = b * c,从 b 读取和c,写入a)。

当你执行 4 个线程时,你有四个不同的 a = b * c;,每个都有三个不同的数据流,导致缓存、内存 Controller 和“打开页面”的更多抖动“[这里的页面是 DRAM 术语,与 MMU 页面无关,但您可能还会发现 TLB 未命中也是一个因素]。

所以你可以通过运行更多线程获得更好的性能,但不是 4 倍,因为每个线程消耗和产生大量数据,内存接口(interface)是瓶颈。除了让机器具有更高效的内存接口(interface) [这可能不是那么容易],您对此无能为力 - 只需接受对于这种特殊情况,内存比计算更像是一个限制因素。

使用多线程解决问题的理想示例是那些需要大量计算但不会占用太多内存的问题。我有一个简单的素数计算器和一个计算“怪异数字”的计算器,当在 N 核上运行时,两者都几乎提供了 Nx 的性能改进 [但我会开始将它们用于比 64 位大很多倍的数字,它会停止给予同样的好处]

编辑:还有可能:

  • 您的代码经常调用的一些函数正在锁定/阻塞其他线程[可能以忙等待的方式,如果实现需要较短的等待时间,如调用操作系统等待几十个时钟- cycles is pointless] - newmalloc 以及它们的释放对应物是合理的候选者。
  • True of false sharing - 数据在 CPU 内核之间共享,导致缓存内容在处理器之间来回发送。从每个线程访问 [和更新] 的特别小的共享数组可能会导致此错误,即使更新是无锁地和原子操作完成的。

当你有这样的事情时,就会使用术语“虚假”共享

 // Some global array. 
 int array[MAX_THREADS];
 ....
 // some function that updates the global array
 int my_id = thread_id();
 array[my_id]++;

虽然每个线程都有自己的数组条目,但同一个缓存行会从一个 CPU 跳转到另一个。我曾经有一个 SMP(在多核之前)dhrystone 基准测试,当在 2 个处理器上运行时,它的性能是一个处理器的 0.7 倍 - 因为经常访问的数据项之一被存储为 int 数组 [MAX_THREADS] 。这当然是一个比较极端的例子……

关于c++ - 为什么 N 个独立计算在 N 个线程上没有快 N 倍?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31442540/

相关文章:

c++ - QThread 卡在第二次运行

c++ - 初始化 struct 类型的常量数组

python - 在 Python 中安排超时

java - CyclicBarrier 线程安全吗?

C++ 错误 : Invalid conversion from 'char' to 'const char*'

c++ - gmake 覆盖编译器目录

objective-c - 从多个线程写入文件

multithreading - Haskell 中的多核编程 - Control.Parallel

multithreading - 如何在多任务环境中测量多线程处理时间?

c++ - 使用多核提升协程