为什么在N个线程上N次独立计算的速度要快N倍?

时间:2015-07-15 22:46:25

标签: 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

为什么会出现这种情况?我完全了解Amdahl的定律,并且多核处理器并不总是快number_of_cores倍,但当我独立时操作,我期待更好的结果。至少在number_of_cores附近。

更新

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

Workload of threads

2 个答案:

答案 0 :(得分:16)

这里的问题是你肯定有一些大块的数字,它们不适合处理器的L1和L2缓存,这意味着当内存控制器跳过整个处理器时,处理器就会坐在它的小ALU手指上。试图为每个处理器读取一点内存。

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

当你做4个线程时,你有四个不同的a,每个都有三个不同的数据流,导致更多的缓存,内存控制器和打开的页面&#34; [这里的页面是一个DRAM术语,与MMU页面无关,但你也可能发现TLB未命中也是一个因素]。

因此,由于每个线程消耗和生成大量数据,因此运行更多线程而不是4x会获得更好的性能,因此内存接口是瓶颈。除了让机器具有更高效的存储器接口[并且可能不那么容易]之外,你无能为力 - 只要接受对于这种特殊情况,存储器不仅仅是一个限制因素。计算。

使用多线程解决的理想示例是需要大量计算但不使用大量内存的示例。我有一个简单的素数计算器和一个计算奇怪的数字&#34;,当在N核上运行时,两者都能提供几乎完全Nx的性能提升[但我会开始使用这些数字比64倍大很多倍的数字比特,它会停止给予同样的好处]

编辑:还有可能:

  • 你的代码调用的一些函数是锁定/阻塞其他线程[可能以忙等待方式,如果实现期望短等待时间,因为调用操作系统等待几十个时钟 - 周期是没有意义的] - 像a = b * c;new这样的东西和他们的自由对手都是合理的候选人。
  • 错误共享 - 数据正在CPU内核之间共享,导致缓存内容在处理器之间来回发送。从每个线程访问[和更新]的特别小的共享阵列可能导致这种情况出错,即使更新是通过无锁和原子操作完成的。

术语&#34; false&#34;当你有这样的东西时会使用分享

malloc

尽管每个线程都有自己的数组条目,但同一个缓存行从一个CPU反弹到另一个CPU。我曾经有一个SMP(在多核之前)dhrystone基准测试,在两个处理器上运行时运行的性能是一个处理器的0.7倍 - 因为其中一个常用的数据项存储为 // Some global array. int array[MAX_THREADS]; .... // some function that updates the global array int my_id = thread_id(); array[my_id]++; 。这当然是一个相当极端的例子......

答案 1 :(得分:-2)

您的答案取决于用户线程或内核线程。如果您正在使用的线程是在用户空间中实现的,那么内核就不会知道它们,因此它们无法在真正的&#34; parallel&#34;中执行。跨多个物理CPU核心的时尚。

如果线程在内核空间中实现,那么内核就会知道这些线程,并且可以跨多个物理cpu核心以并行方式处理它们。

线程创建,销毁和上下文切换也有开销。每次线程上下文切换时,线程库都需要存储值和加载值等。