【问题标题】:Why isn't the multithreaded loop faster?为什么多线程循环不更快?
【发布时间】:2017-04-16 17:06:25
【问题描述】:

我刚刚阅读了this intro 以使用 openMP 进行并行处理。

我尝试了以下简单的代码

#include <iostream>
#include <ctime>
#include <vector>

int main()
{
        // Create an object just to allow the following loops to do something
        std::vector<int> a;
        a.reserve(2000);

        // First single threaded loop
        std::clock_t begin;
        std::clock_t end;
        begin = std::clock();
        double elapsed_secs;

        for(int n=0; n<1000000000; ++n)
        {
                if (n%100000000 == 0) a.push_back(n);
        }

        end = std::clock();

        elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
        std::cout << "Time for single thread loop: " << elapsed_secs << std::endl;

        // Second multithreaded loop
        begin = std::clock();
        #pragma omp parallel for
        for(int n=0; n<1000000000; ++n)
        {
                if (n%100000000 == 0) a.push_back(n);
        }

        end = std::clock();
        elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
        std::cout << "Time for multi thread loop: " << elapsed_secs << std::endl;

        return 0;
}

已用g++ -std=c++11 -o a a.cpp -fopenmp 编译,输出

Time for single thread loop: 3.9438
Time for multi thread loop: 3.94977
  • 我是否误解了如何在 C++ 中进行并行化
  • 我是否误解了如何编译?
  • 代码是否已并行化,但无论出于何种原因,速度的提升并不明显?

请注意,我的机器上有 12 个内核(当前没有运行大进程)。

【问题讨论】:

  • 会不会是优化问题?
  • 你有一个依赖,因为a在每个循环处理之间是通用的。
  • 哪个更快,将一个开关拨动一千次,还是让数千人每人拨动同一个开关一次?我很惊讶并行版本并不慢。猜猜 OMP 做了理智的事情,没有产生任何额外的线程。
  • 我尝试将a.push_back(n); 替换为std::cout &lt;&lt; n &lt;&lt; std::endl;,但性能仍然没有差异
  • 好的,所以不是按一千次名为“a”的同一个开关,而是尝试按一千次名为“std::cout”的开关。提示:std::cout 是单个对象。

标签: c++ multithreading c++11 multiprocessing


【解决方案1】:

std::clock 测量的不是实时而是 CPU 时间。最好使用std::chrono 作为其他答案的建议。

或者为了在不更改代码的情况下进行快速测试,请在 shell 中尝试:

date; time ./a; date

这是输出:

jue dic 1 23:12:57 CET 2016

单线程循环时间:2.99741

多线程循环时间:4.55788

真正的 0m4.184s

用户 0m7.556s

系统 0m0.000s

jue dic 1 23:13:01 CET 2016

时间与您的输出不同。实际时间在我的电脑中大约是 4 秒,而不是像您的程序输出那样是 7.5 秒。

您应该阅读有关 std::clock() 的文档,具体来说:

例如,如果CPU被其他进程共享,std::clock time 可能比挂钟前进得慢。另一方面,如果当前 进程是多线程的,并且有多个执行核心 可用,std::clock 时间可能会比挂钟快。

【讨论】:

    【解决方案2】:

    std::clock 测量 CPU 时间,而不是 wall 时间(至少 gcc 实现,尽管我相信 MSVC 实现测量 wall 时间)。这是cppreference的摘录:

    返回自与程序执行相关的实现定义时代开始以来进程使用的近似处理器时间。要将结果值转换为秒,除以 CLOCKS_PER_SEC。

    只有对 std::clock 的不同调用返回的两个值之间的差异才有意义,因为std::clock 时代的开始不必与程序的开始重合。 std::clock 时间可能会比挂钟快或慢,这取决于操作系统为程序提供的执行资源。例如,如果 CPU 被其他进程共享,std::clock 时间可能会比挂钟慢。另一方面,如果当前进程是多线程的并且有多个执行内核可用,std::clock 时间可能会比挂钟快。

    您可以使用std::chrono 设施测量挂墙时间:

    auto Begin = std::chrono::high_resolution_clock::now();
    // ...
    auto End = std::chrono::high_resolution_clock::now();
    std::cout << "Time for xxx: " << std::chrono::duration_cast<std::chrono::milliseconds>(End - Begin).count() << std::endl;
    

    你会看到真正的加速。

    顺便说一句,我想说你的测试不是线程安全的,因为push_back 需要修改你的向量的end 位置。

    【讨论】:

      【解决方案3】:

      我认为您第二个循环较慢的原因仅仅是线程所需的开销。您正在做的工作非常少,而且往往本身就很快。 Push_back 是常量,该函数只是使用指向容器末尾的指针来添加新项目,然后更新指向“end”的指针。我认为,如果您将更复杂的代码放入循环中,您会开始看到不同之处。我还注意到您从未清除循环之间的“a”,因此您在“a”中添加了额外的百万个项目,这可能会导致第二个循环(即使它没有线程化)运行速度变慢。

      我不认为您误解了如何进行线程,就像您忽略了执行线程所需的开销(包括创建、inti、上下文切换、ext)一样。

      这个问题似乎很常见,但同时每个问题的答案都可能非常不同,因为线程中有很多事情(https://unix.stackexchange.com/questions/80424/why-using-more-threads-makes-it-slower-than-using-less-threads)。在那个链接中,答案更广泛,指出线程速度非常依赖于系统性能,例如 CPU 资源、RAM 资源和网络 i/o 资源)。此链接:Why is my multi-threading slower than my single threading? 显示 OP 正在写入控制台,这就是问题所在(根据链接,控制台类处理线程同步,因此内置类中的代码是单线程的原因跑得更快)。

      【讨论】:

        【解决方案4】:

        还应该说,您在并行循环中所做的事情是没有意义的,并且必然会导致运行时错误。有两个问题。

        第一

        #pragma omp parallel for
        for(int n=0; n<1000000000; ++n)
        {   if(n%100000000 == 0) <some code>    }
        

        任何事情只做 10 (10) 次。编译器可能会优化循环变量n,而你留下的代码相当于

        #pragma omp parallel for
        for(int n=0; n<10; ++n)
        {   <some code>   }
        

        只有在&lt;some code&gt; 对计算的要求很高时才能从并行性中受益。所以,你实际上没有测试任何东西。

        其次,更严重的是,

        a.push_back(n);
        

        不是线程安全的。也就是说,您不能(可能)从不同的线程同步调用它。每次调用 std::vector::push_back() 都会更改向量的状态,即其内部数据,从而导致竞争条件。

        最后,我应该建议不要使用 OpenMP 来实现与 C++ 的并行性,因为它不支持/利用 C++ 语言功能(例如模板),甚至没有针对最近的 C++ 标准进行标准化。请改用 tbb 之类的东西,它是为 C++ 设计的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-12-25
          • 1970-01-01
          • 2022-09-24
          • 2020-05-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多