【问题标题】:Why can't I get any performance improvements by running multiple threads in C++11?为什么我不能通过在 C++11 中运行多个线程来获得任何性能改进?
【发布时间】:2016-07-02 17:05:12
【问题描述】:

我有以下测试程序,它有一个简单的函数,可以找到我试图在多个线程中运行的素数(仅作为示例)。

#include <cstdio>
#include <iostream>
#include <ctime>
#include <thread>

void primefinder(void)
{
   int n = 300000;

   int i, j;
   int lastprime = 0;
   for(i = 2; i <= n; i++) {
      for(j = 2; j <= i; j++) {
           if((i % j) == 0) {
               if(i == j)
                   lastprime = i;
               else {
                   break;
               }
           }
      }
   }

   std::cout << "Prime: " << lastprime << std::endl;
}

int main(void)
{
   std::clock_t start;
   start = std::clock();

   std::thread t1(primefinder);
   t1.join();

   std::cout << "Time: " << (std::clock() - start) / (double)(CLOCKS_PER_SEC / 1000) << " ms" << std::endl;

   start = std::clock();

   std::thread t2(primefinder);
   std::thread t3(primefinder);
   t2.join();
   t3.join();

   std::cout << "Time: " << (std::clock() - start) / (double)(CLOCKS_PER_SEC / 1000) << " ms" << std::endl;
   return 0;
}

如图所示,我在 1 个线程中运行该函数一次,然后在 2 个不同的线程中运行一次。我使用-O3 和-pthread 用g++ 编译它。我在 Linux Mint 18 上运行它。我有一个 Core i5-4670。我知道它归结为操作系统,但我非常希望这些线程在某种程度上并行运行。当我运行程序时,top 在使用 1 个线程时显示 100% CPU,在使用 2 个线程时显示 200% CPU。尽管如此,第二次运行所需的时间几乎是原来的两倍。

CPU 在运行程序时什么也不做。为什么不并行执行?

编辑:我知道两个线程都在做同样的事情。我选择primerfinder函数只是作为一个令人尴尬的并行示例,所以当我在多个线程中运行它时,它应该需要同样长的实时时间。

【问题讨论】:

  • 如果您希望操作系统同时在两个内核中运行线程,您将需要一些操作系统特定的 API 调用;最好是告诉操作系统在单独的内核上运行的调用。否则,操作系统将与系统中的所有线程、任务和可执行文件进行循环,您的线程可能不会并行运行(它们可能会被交换到操作系统方便的执行点)。
  • 您可以尝试使用cpu_set_t(在linux中)显式设置核心并提供较低的nice值。
  • 我看不出多个线程如何相互补充。他们正在调用相同的确切功能。看来您只是多次执行相同的操作。也许您希望一个线程搜索素数 > 200000,而另一个线程搜索那些
  • 一些可以防止线程加速的事情(与您的代码没有直接关系,只是想我会提到它们)是:1)错误共享 2)同步开销 3)调度开销 4)线程启动时间开销。特别是如果并行完成的工作不是很好。
  • @thomas 那是废话

标签: c++ multithreading c++11 parallel-processing


【解决方案1】:

在 c++ 中使用 std::clock 为并行程序计时是非常具有欺骗性的。在为程序计时时,我们关心两种类型的时间:wall time 和 cpu time。墙上时间是我们都习惯的(想想墙上的时钟)。 Cpu 时间本质上是您的程序使用了多少个 cpu 周期。 std::clock 返回 cpu 时间(这就是你除以 CLOCKS_PER_SEC 的原因),并且当有一个执行线程时,cpu 时间仅等于壁时间。如果一个程序可以 100% 并行运行(就像你的一样),那么 cpu 时间 =(线程数)*(墙上时间)。因此,看到的时间几乎是您所期望的两倍。

对于测量墙上时间(这是您想要做的),我不知道使用 STL 的方法。您可以使用 OpenMP 或 Boost 对其进行测量。

omp_get_wtime()

Boost Timer

由于您使用的是 linux,因此您使用的 g++ 版本很可能内置了 openmp 支持。

#include <omp.h>

const double startTime = omp_get_wtime();
..... //Work goes here

const double time = omp_get_wtime() - startTime;

您必须使用 -fopenmp 进行编译

编辑:

正如 johnbakers 指出的那样,chrono 库确实有一个 wall clock

#include <chrono>

auto start = std::chrono::system_clock::now();
.... //Do some work

auto end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end - start;
std::cout << "Time: " << diff.count() << "(s)" << std::end;

与加速计时器的输出:

Boost: 121.685972s wall, 724.940000s user + 67.660000s system = 792.600000s CPU  (651.3%)
Chrono: 121.683(s)

【讨论】:

  • std::chrono 中的所有工具怎么样?
  • @LightnessRacesinOrbit 答案更有可能是使用与标准库类似的通用术语 STL,而不是您所指的旧原始 STL,因为这更有可能在c++11 和一般来说,我认识的大多数专业 c++ 开发人员仍然使用短语 STL 来指代 current 标准库,即使这不是准确的用法术语。
  • @LightnessRacesinOrbit 对 site:ycombinator.com 的简单搜索“stl”将显示该术语最常用的方式,无论其准确性如何
  • 别担心,你和大约 90% 的 c++ 开发人员。常见的错误,任何有理智的人都会知道你的意思。
  • @LightnessRacesinOrbit 确实,您对如何使用“stl”的永久执着妨碍了有意义的讨论,这令人失望的。
【解决方案2】:

您的设计中有一个非常基本的问题,这解释了为什么您看不到线程的任何好处。

当您遇到这样的搜索问题并希望通过并行化加快它的速度时,通常的想法是使用分而治之。您需要以某种方式划分工作,以便例如第一个线程做前半部分工作,第二个线程做后半部分工作。

在您的代码中,两个线程调用完全相同的函数并且它们不进行通信——它们都复制了其他人的工作!

在某些问题中,分工很容易。例如,如果您正在处理SAT solver,则在两个线程之间分配工作的一种方法是选择一个变量x_1,比如说,第一个线程将假定x_1 = true 并检查公式是否可满足,第二个线程将假定x_1 = false 并检查公式是否可满足。然后,当它们都加入时,您将知道整个公式是否可以满足,并且不需要互斥锁或线程间通信。

在您的素数问题中,它有点复杂。您可以尝试执行类似的操作,第一个线程只考虑以1, 3, 5 结尾的候选人,第二个线程考虑以7, 9 结尾的候选人。不过,为了获得最佳性能,您可能希望使用“Eratosthenes' Sieve”之类的东西,我认为并行化会有点困难。 (你也许可以使用一个原子数组?)

【讨论】:

  • 恐怕你错过了我的观点,primerfinder函数只是一个例子。我真正的问题是执行两次工作不应该花费两倍的时间,因为它可以并行化。不过感谢您的回答。
【解决方案3】:

代码不并行的原因有很多可能

在过去,操作系统会以循环或优先方式运行可执行文件。所以一个执行线程可以运行几毫秒,然后换成另一个执行线程。这将给出执行线程并行运行的外观

当一个线程等待资源并且资源不可用时,也会发生执行线程的换出。

在具有多个处理器或内核的现代计算机中,该技术仍然相同。操作系统有另一个处理器可以将任务委托给它。核心时间很宝贵。操作系统不太可能停止多个内核上的所有正在运行的任务,因此您的线程可以并行执行。这可能意味着操作系统必须等待一个处理器完成,以便您的线程可以同时执行。很可能不会发生。

但是,许多操作系统都有属性,您可以设置这些属性来告诉它们让您的线程独占访问一个或多个内核。由于这不是标准的 C++ 功能并且操作系统也不尽相同,因此您必须查找 API 或任何编译器特定的支持。

编辑 1:中断和其他任务
请记住,您的平台并非专门运行您的程序。其他任务正在潜伏,并且可能在您的程序运行时正在执行。一些例子包括:病毒检查器、ping 互联网的东西和音乐播放器(至少在我的机器上)。

这些应用程序和中断可能会导致操作系统在同一处理器上而不是并行地与您的线程进行循环。曾经的场景是将音乐播放器放在一个处理器上,为什么您的程序在另一个处理器上运行。

【讨论】:

    【解决方案4】:

    看看std::chrono namespace。它提供了许多 C++11 实用程序来测量时间,这些实用程序优于迄今为止的所有建议。

    例如,您可以cast 将时钟读取到墙上时间。

    定时编程代码并非易事,但这些工具确实使沿着这些思路进行探索变得更加容易。

    【讨论】:

      猜你喜欢
      • 2021-06-13
      • 2021-02-27
      • 1970-01-01
      • 2020-11-14
      • 2018-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多