多线程比单线程慢

Posted

技术标签:

【中文标题】多线程比单线程慢【英文标题】:Multithreading slower than single threading 【发布时间】:2021-05-04 14:13:46 【问题描述】:

我编写了一个程序,可以实时对大型数组进行大量计算。可以将任务拆分为多个子数组以进行多线程处理。但是,我无法使用线程更快地运行它。

这是为演示而创建的示例虚拟代码(同样的问题)。 两个线程版本最终会持续 39 秒,如果它们一个接一个地计算,则要多几秒(!)。数组是否是全局的等都没关系。我也只使用“线程构造函数”测试过一次,但结果相同。

我正在使用 XCode (5.1.1) 和 Macbook Air(2013 型号,Core i5,Os X 10.8.5)。是的,这是旧电脑,我很少编程……

那么,你能发现我在代码中的逻辑有什么错误吗?或者它可能是在 Xcode 等的设置中的某个地方?

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

class Value

public:
    float a[3000000];
;

void cycle(Value *val)

    int i;
    for (i=0; i<3000000; i++)
        
            val->a[i]=n;
            n+=0.0001;
        


int main()

    Value *val1=new Value, *val2=new Value;
   
    clock_t start,stop;
   
    start=clock();
    for (int i=0; i<1000; i++)
    
        thread first (cycle,val1);
        thread second (cycle,val2);
        first.join();
        second.join();
    
   
    stop=clock();
    float tdiff=(((float)stop - (float)start) / 1000000.0F);
    std::cout<<endl<<"This took "<<tdiff<<" seconds...";
    return 0;

'''

【问题讨论】:

你如何编译这个?你调整优化了吗?单线程代码看起来如何?请显示minimal reproducible example,测量性能有很多微妙之处,尤其是在处理多线程时。 在任何地方都没有声明n。请发布您已编译并运行的代码。 如果此处未包含的n 是全局变量,那么您没有得到加速是有道理的。共享全局状态可以让你的代码基本上以串行模式运行,甚至更糟。 有一个笑话是这样的:“一个程序员需要 1 天来完成一个程序,10 个程序员需要多少天? - 10 天。”您正在生成并立即加入循环中的线程,这使您的代码具有很强的顺序性。 @churill 你可能是对的,显然它是衡量某些东西的精简示例。我被取笑写了一个答案,否则我会坚持要澄清;)。然而,与顺序版本相比,这种产生和加入线程很可能是 OP 看到的最大差异 【参考方案1】:

有一个笑话是这样的:

一个程序员完成一个程序需要1天,10个程序员需要多少天? - 10 天。

您的代码中的工作是在这个循环中完成的:

for (int i=0; i<1000; i++)

    thread first (cycle,val1);
    thread second (cycle,val2);
    first.join();
    second.join();

现在考虑产生和加入线程是开销。总的来说,您的并行代码比顺序代码所做的要多,一般来说,没有办法解决这个问题。而且您不是创建和加入线程一次,而是1000-times,即您添加了1000-times 开销。

不要期望代码通过简单地添加更多线程来运行得更快。我把你推荐给Amdahl's law 或Gustavson's Law (基本上都是一样的,只是更积极一点)。

我建议您尝试使用顺序与线程,但只使用一个线程来感受开销。你可以比较一下:

for (int i=0; i<1000; i++)

    thread first (cycle,val1);
    first.join();

使用不使用任何线程的顺序版本。您会惊讶于这种差异。

当线程完成大量工作(参见 Amdahl/Gustavson)并且不同线程之间没有同步时,您可以充分利用多线程。您的1000 加入线程的时间基本上是一个障碍,second 必须等待first 完成之前什么都不做。最好避免此类障碍。

最后但并非最不重要的一点是,正如评论中提到的,您的基准相当有问题,因为您没有使用计算结果。也就是说,要么您没有打开优化,这使结果变得毫无意义,要么您确实打开了优化,编译器可能会在您没有注意到的情况下优化事情。实际上,我不确定您是否在比较执行相同工作的两个版本,或者您的并行版本是否正在做两倍的工作。此外,在测量时间时,您需要注意测量挂钟时间而不是 CPU 时间,因为 CPU 时间会增加在多个内核上花费的时间,而您想比较挂钟时间。

TL;DR:更多线程!= 自动减少运行时间。

【讨论】:

感谢您的评论。我承认我在这方面表现得有些糟糕。 1000 次重复展示了实时计算的性能。它可用于获取每秒帧数 (FPS)。无论如何,现在它可以工作了!杀戮元素是“时钟”功能,显示时间不正确。现在速度快了 100%,因为它应该使用两个线程。 @BearProductions 抱歉,但“现在速度快了 100%,因为它应该使用两个线程”听起来你忽略了我在答案中写的所有内容:(。使用两个线程而不是一个 不应该快两倍。我不知何故错过了实际问题,但我写的确实适用。【参考方案2】:

如果您阅读了clock 的文档,您可能会注意到,如果进程在多个内核上执行,时间会显得更快; clock 是 CPU 使用总量的近似值,而不是“挂钟时间”,两个并行内核上的一个“CPU 滴答声”与一个内核上的两个连续“滴答声”的“时间”相同。 (顺便说一句:为了得到以秒为单位的时间,你应该除以CLOCKS_PER_SEC。)

使用更合适的计时器,如std::chrono::steady_clock,将显示顺序变体所用的时间几乎是多线程版本的两倍。 这种差异完全可以通过创建和销毁线程的开销来解释。

【讨论】:

std::chrono::high_resolution_clock 不一定是单调时钟,因此也不太适合测量时序。 std::chrono::steady_clock 是。 感谢 molbdnilo!这真的解决了问题。更改为“std::chrno::high_resolution_clock”揭示了计算中的真实时间。 “时钟”显示时间不正确。现在,当使用三种威胁时,代码实际上快了 130%。【参考方案3】:

与其他 cmets 建议的不同,n 不是罪魁祸首,顺序连接也不是。 每个线程执行相同的操作cycle,那怎么可能有任何改进呢? 您必须在两个线程之间手动拆分工作负载,以便每个线程处理一半的数据。

【讨论】:

以上是关于多线程比单线程慢的主要内容,如果未能解决你的问题,请参考以下文章

多线程比单线程慢

多线程函数性能比单线程差

C++11 多线程比单线程慢

多线程并发一定比单线程快吗?

python多线程不能利用多核cpu,但有时候多线程确实比单线程快。

java - 多线程中的简单计算比单线程中需要更长的时间