多线程比单线程慢
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
,那怎么可能有任何改进呢?
您必须在两个线程之间手动拆分工作负载,以便每个线程处理一半的数据。
【讨论】:
以上是关于多线程比单线程慢的主要内容,如果未能解决你的问题,请参考以下文章