簡體   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