繁体   English   中英

等待线程如何影响性能?

[英]How do waiting threads affect performance?

我正在编写一个程序,当达到最坏的情况时,该程序的计算将相对昂贵。 我已经尝试过动态创建线程,并且事实证明,它在大多数情况下都可以工作,但是当出现最坏情况时,执行速度超出了我分配完成这些计算的时间,这在很大程度上是由于创建和这些线程的破坏。 这使我想到了过去使用的想法,即在执行之前创建线程,而不是动态创建和销毁它们,而是让它们在执行计算之前先等待条件,而不是动态创建它们。

通常,我不会三思而后行,但是由于在系统初始化时会创建很多线程,因此我担心这将如何影响系统性能。 这就提出了一个问题:正在等待条件的线程如何影响系统? 是在程序初始化期间创建线程并仅在需要执行计算时通知线程才是解决此问题的正确方法,还是存在我不知道的更好的解决方案? 我也考虑过使用线程池来执行此操作。 线程池是否最适合这种情况?

您可能会发现一些有助于更好地回答此问题的信息:

-我正在使用boost库(版本1_54_0)以对程序进行多线程处理。

-我正在使用Windows 7和Visual Studio。

-如果我在程序初始化时创建线程,则我将创建200-1000个线程(此数字已预先定义为#define,并且每次执行计算时都不必使用所有线程)。

-每次执行此计算时,所需的线程数都会有所不同; 它取决于每次执行计算时都会改变的接收到的输入数量,但决不能超过最大值(在编译时将最大数量确定为#define)。

-我使用的计算机具有32核。

如果这个问题没有达到标准,我感到抱歉。 我是一个新的堆栈溢出用户,所以随时索取更多信息并批评我如何更好地解释这种情况和问题。 预先感谢您的帮助!

更新

这是源代码(一些变量已按照我公司的条款和条件重命名)

for(int i = curBlob.boundingBoxStartY; i < curBlob.boundingBoxStartY + curBlob.boundingBoxHeight; ++i)
{
    for(int j = curBlob.boundingBoxStartX; j < curBlob.boundingBoxStartX + curBlob.boundingBoxWidth; ++j)
    {
        for(int k = 0; k < NUM_FILTERS; ++k)
        {
            if((int)arrayOfBinaryValues[channel][k].at<uchar>(i,j) == 1)
            {
                for(int p = 0; p < NUM_FILTERS; ++p)
                {
                    if(p != k)
                    {
                        if((curBlob.boundingBoxStartX + 1 < (curBlob.boundingBoxStartX + curBlob.boundingBoxWidth)) && ((int)arrayOfBinaryValues[channel][k].at<uchar>(i + 1,j) == 1))
                            ++count;

                        if((curBlob.boundingBoxStartY + 1 < (curBlob.boundingBoxStartY + curBlob.boundingBoxHeight)) && ((int)arrayOfBinaryValues[channel][k].at<uchar>(i,j + 1) == 1))
                            ++count;
                    }
                }
            }
        }
    }
}

提供的源代码严格显示了算法的复杂性。

如果线程确实在等待,那么它们根本不会消耗太多资源-仅占用一点内存,而在调度程序的等待列表中只有几个“空间”插槽(因此,将会有少量额外开销) “唤醒”或“等待”线程,因为还有更多数据要处理-但是这些队列通常效率很高,所以我怀疑您是否能够在实际线程在其中进行有意义工作的应用程序中进行测量)。

当然,如果它们定期唤醒(即使是每秒一次),那么每秒唤醒一次的1000个线程意味着每毫秒要进行一次上下文切换,这可能会影响性能。

但是,我确实认为在几乎所有情况下创建许多线程都是错误的解决方案。 除非线程中的逻辑很复杂,并且每个线程要跟踪大量的状态/上下文,并且此状态或上下文不容易存储在某个位置,否则这样做是正确的。 但是在大多数情况下,我会说使用少量的工作线程,然后让工作项队列(包括对它们各自的状态或上下文的(某种类型的引用))将是实现此目的的更好方法。

基于有关编辑编辑

由于(据我所知)线程完全受CPU(或内存带宽)的约束-没有I / O或其他“等待”,因此,通过在系统中每个内核运行一个线程可以实现最大性能。 (对于“其他需要完成的工作,例如通过网络,磁盘I / O和需要完成的常规OS /系统工作进行通信”,可能为“减一”)。

如果准备好运行的线程数多于CPU上的核数,则线程数多于内核数甚至可能导致处理变慢,因为现在操作系统将有多个线程在争夺时间,这将使导致操作系统方面额外的线程调度工作,最重要的是,当一个线程运行时,它将为缓存加载有用的内容。 当另一个线程在同一CPU内核上运行时,它将迫使缓存将其他数据加载到缓存中,并且当“旧”线程再次运行时,即使它在同一CPU上,也必须重新加载它正在使用的数据。

我将做一个快速实验,并为我的一个项目返回一些数字...

因此,我有一个小项目,可以计算“ 怪异数字 ”。 在这里,我将其用作“比较运行一个线程与更多线程所花费的时间”。 这里的每个线程使用的内存都很少-数百个字节,因此缓存可能根本不起作用。 因此,这里唯一的变量是“启动成本”和线程之间竞争导致的边际开销。 线程数由-t选项决定。 -e是“要停止的数字”。

$ time ./weird -t 1 -e 50000 > /dev/null

real    0m6.393s
user    0m6.359s
sys 0m0.003s
$ time ./weird -t 2 -e 50000 > /dev/null

real    0m3.210s
user    0m6.376s
sys 0m0.013s
$ time ./weird -t 4 -e 50000 > /dev/null

real    0m1.643s
user    0m6.397s
sys 0m0.024s
$ time ./weird -t 8 -e 50000 > /dev/null

real    0m1.641s
user    0m6.397s
sys 0m0.028s
$ time ./weird -t 16 -e 50000 > /dev/null

real    0m1.644s
user    0m6.385s
sys 0m0.047s
$ time ./weird -t 256 -e 50000 > /dev/null

real    0m1.790s
user    0m6.420s
sys 0m0.342s
$ time ./weird -t 512 -e 50000 > /dev/null

real    0m1.779s
user    0m6.439s
sys 0m0.502s

如您所见,“运行”整个项目的时间从1减少到2,从2减少到4。 但是运行四个以上的线程会得到几乎相同的结果,直到达到数百个为止(我跳过了将线程数加倍的几个步骤)。

现在,为了显示调度开销,我在-e之后用更大的数字增加了“要查找的数字”的数量(这也使该过程运行更长的时间,因为更大的数字计算起来更复杂)。

$ time ./weird -t 512 -e 100000 > /dev/null

real    0m7.100s
user    0m26.195s
sys 0m1.542s
$ time ./weird -t 4 -e 100000 > /dev/null

real    0m6.663s
user    0m26.143s
sys 0m0.049s

现在,如果仅由启动时间决定成本,我们应该看到在512个线程达到50000个线程和在512个线程达到100000个线程之间的开销(在sys类似的),但我们看到的开销要高出三倍。 因此,在6到7秒内,以全速运行512个线程与运行4个线程相比,浪费了将近1.5s的CPU时间(或每个CPU约0.4s)。 当然,只有5%左右,但仍然有5%的工作浪费了。 在许多情况下,“值得”是对算法的5%改进。

是的,这是一个极端的情况,可以争辩说,只要大多数线程都在等待,那并不重要。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM