單核CPU,多線程與性能


 

 

問題概述

單核CPU的計算機上, 多線程能夠提高程序運行的性能嗎? 這個問題看起來簡單,實際很復雜,設計到多方面的因素. 首先我們要把概念搞清楚, 那就是什么是性能? 一般來說, 我們把運行一個任務所花的時間來評價性能, 所花的時間可以是在CPU上, 也可能是在I/O操作上, 運行任務的程序, 也可能同時在運行另外若干的任務(吞吐量). 這里我們把概念給縮小一下:

 

我們這里把性能限制在一個程序運行一個任務, 這個任務是只消耗CPU資源(CPU bound), 所花的時間越小, 說明性能越好. 為了純粹地說明問題, 我們排除了數據共享問題, 即線程之間不做任何同步動作, 完全隔離.

 

從理論上說,如果計算機只執行這一個測試程序, 那么單線程要比多線程性能好,因為多線程需要做線程上下文環境的切換; 而當計算機同時運行其他的進程, 假設其他進程里也有多個大量消耗CPU的任務, 那么我們的程序由於是多線程, 搶到CPU時間片的機會增多, 它的性能應該好於單線程.

 

理論上是這么回事, 但我們知道, 實踐與理論是有差距的, 我們的測試不可能在真空環境中. 操作系統的實現有高度的戲劇性, 誰也不能預測實際的測試一定與理論相符, 另外在我們實際的運行環境中,各種情況導致的系統差異很大, 因為我們必須做一些測試.

 

MSDN上有一篇著名的文章<<Win32 Multithreading Performance>>, 里面講的東西與我們很相近.它主要論述的是串行計算和並行計算的性能比較, 我們直接拿它的例子, 進行一些更改, 來測試我們的假設.

 

首先討論我們測試的主題與它的不同, 主要有2點: 1, 我們討論的任務是一個固定的任務, 而它里面討論的是數個計算量不同的任務, 而計算不同的任務涉及到吞吐量的概念; 2, 我們討論的是線程數量之間的比較(分別測試不同數量的線程), 而它只有單線程與多線程的比較.

圖1: 多個不同的任務並行處理

 

圖案2: 一個固定的任務並行處理

 

測試程序介紹

一個任務在這里簡化為一個持續占用CPU的計算, 為了測試的准確性, 中間不可以有任何的I/O操作, 例如:

   for (int iCounter=0; iCounter<iLoopCount; iCounter++);

 

給定的任務量iDelay,單位是毫秒, 有一段模擬的代碼, 從而把一秒的時間轉換為循環數iLoopCount.里面所用的兩個API是QueryPerformanceFrequency和QueryPerformanceCounter.

 

我們增加一個方法來取代OnWorstcase:

void CThreadlibtestView::OnFixTask()

{

    if(!m_iNumberOfThreads)

    {

     MessageBeep(-1);

     return;

    };

 

    for (int iLoop=0;iLoop<m_iNumberOfThreads;iLoop++)

        {

         m_iNumbers[iLoop]=(int)&m_tbConc[iLoop];

         m_tbConc[iLoop].iId=iLoop;

 

         if(iLoop==0)

            m_tbConc[iLoop].iDelay=m_iDelay/m_iNumberOfThreads+m_iDelay%m_iNumberOfThreads;

         else

            m_tbConc[iLoop].iDelay=m_iDelay/m_iNumberOfThreads;

 

         m_tbConc[iLoop].tbOutputTarget=this;

     m_tbConc[iLoop].iStartOrder=0;

     m_tbConc[iLoop].iEndOrder=0;

     m_tbConc[iLoop].iTouchCount=0;

        };

}

 

另外我們指定特定的任務量和線程數量:

int iTaskSize[]={100,500,1000,2000,4000};

int iThreadSize[]={2,5,10,15,20};

單線程時候我們調用OnSerial(), 多線程時調用OnConcurrent(), 線程數量分別取iThreadSize里的值.

 

測試次數仍然保持10次, 取平均值, 整個測試我們分別運行兩次, 第一次在負載很輕的計算機上運行, 第二次在同樣的計算機上加載一個大負載的進程, 此進程里有十個線程, 每個線程都是基於CPU的密集計算, 程序如下:

DWORD WINAPI ThreadFunc(LPVOID lpParam)

{

        DWORD busyTime=10;

        while(true)

        {

                DWORD startTime=GetTickCount();

                for(;GetTickCount()-startTime<=busyTime;)

                        ;

                Sleep(1);

        }

        return 0;

}

 

特別要注意的是, 兩次計算不能各啟動一個進程, 必須在一個進程中, 因為在程序開始,我們會算出1秒對應循環數, 而每次啟動程序這個數字是不同的, 為了更有意義的比較,我們要求這個數字一定相同.

 

測試環境: CPU Intel Pentium M 1.7 G; 1047472KB RAM, Windows 2000 professional SP4.

一秒的循環數:146278208

 

測試結果

3: 在負載輕的系統上測試結果

4: 在負載重的系統上測試結果

 

結論和建議

根據測試結果,我們可以得出一些結論(針對單核CPU):

在負載輕的系統上, 多線程不適合處理基於CPU的任務,而在負載重的系統上,多線程可以幫助提高性能.

 

這是一個模糊和慎重的結論, 因為測試結果里的一些現象我也給不出合適的理由, 應該屬於操作系統戲劇化不確定性的表現吧(操作系統會對某些線程動態提高優先級,但本例中的線程優先級保持不變.), 例如在負載輕的機器上, 20個線程和2個線程運行時間差異不大; 在負載重的機器上, 2個線程運行時間比單線程還長, 而當線程數量增加到5時, 性能才有顯著的提高.

 

所以我的建議是,在單核CPU機器上,處理CPU密集的任務時, 不推薦使用多線程, 除非你對目標機器非常的了解和確定,並經過嚴格的測試. 當然, 涉及到其他I/O操作的任務, 比如等待用戶按鍵, 讀取文件, 網絡通訊等, 多線程才是正當其選的解決方案.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM