Intel多線程技術的使用與優化——《多核多線程技術》

1、幫助測試的軟件

1. Intel VTune性能分析器

       Intel VTune性能分析器能夠幫助定位程序中與性能有關的問題。其在Windows下支持圖形化界面,可支持命令行輸入。主要功能有:算法

        取樣功能能夠幫助開發者定位程序中最消耗時間的函數和模塊;編程

         曲線圖可肯定程序運行時函數調用順序和顯示關鍵路徑;安全

         計數器監控器肯定是否會由於可用內存減小或文件輸入/輸出而致使程序速度變慢;數據結構

         調優助手自動推薦代碼改進方法。多線程

2. MKL數學核心函數庫

         該庫利用Intel多核處理器,提供高度優化的函數,使程序得到更高性能並減小開發時間。併發

3. Thread Checker線程檢查器

          快速查找和修復Windows和openMP軟件中的線程bug(好比數據競爭),提示同線程錯誤相關的源代碼位置。編程語言

 

2、與體系無關的優化方法

1.編譯優化選項

           對於同一個函數來講,調用編譯器不一樣的優化選項,產生的結果可能不一樣。分佈式

編譯選項 函數1讀寫次數 函數2讀寫次數 讀寫所用時間
-g 8 read,2 write 6 read,2 write 0.21e-3/0.21e-3
-O2 1 read,1 write 1 read,0 write 0.3e-3/0.7e-5
-O3 2 read,1 write 2 read,1 write 0.27e-3/0.27e-3

       從中可看出,-O3不必定比-O2好,-O3採用inline技術,將函數直接嵌入main函數中,這樣是否能就得看程序具體狀況。函數

2.減小沒必要要的內存存取

void combine(data_t *dest)
{
     int length = vec_length(v);
      for(int i=0;i<length;i++)
          *dest += data[i];
}

        在combine函數中,*dest函數屬於傳入參數,是經過堆棧被函數體內部引用的。所以不管是讀寫,都要進入系統內存,成本太高。其實只要用一個局部變量,存儲中間計算結果,最後傳給dest就好了。性能

3.函數調用的邊際效應

int counter = 0;
int f(int x)
{
   return counter++;
}

int func1(x)
{
    return f(x)+f(x);
}

 

int counter = 0;
int f(int x)
{
   return counter++;
}

int func2(x)
{
    return 2*f(x);
}

 

       只要函數調用過程當中改變了某些全局變量的值,就稱函數調用有邊際效應。存在邊際效應的函數因調用次數對程序有不一樣影響。減小邊際效應的方法就是 減小全局變量的使用。

4.數據結構

        C語言中16位int數據結構,其數據範圍在-32768~32767。但實際使用時,用戶可能只用int來定義一個枚舉類型。

 

3、多線程編程常見問題

1.串行化

        雖然說多線程是併發的,但如遇到兩個線程要讀寫同一個數據,爲了數據安全,必然是一個線程先對數據進行操做另外一個線程在進行操做。這樣在數據部分多線程其實是串行化運行。

        第一個解決方法是無鎖算法,但過於複雜,並且容易出錯。

        第二個解決方法是原子操做,本質上沒有解決串行化問題,但可讓串行化速度提高。惋惜目前廠商提供的原子操做有限。

        第三個解決方法是從設計層面來縮小串行化比例。使用任務分解模式、數據分解模式、數據共享模式等。

注:運行在雙CPU或四CPU上的程序,因爲鎖競爭致使的加速係數降低現象不明顯。但隨着CPU核數增多,這個問題會變得很嚴重。多核編程與多任務編程是不同的。

2.線程過多

       將給定工做量劃分給過多線程,會形成線程啓動和終止的開銷比實際運行的開銷更大。並且,過多的會致使共享有限硬件資源的開銷增大。

       建議使用線程池。

3.數據競爭與死鎖

 

4、非阻塞算法

        非阻塞算法的本質是中止一個線程執行不會阻礙其餘執行實體的運行,其特色在於:

         1)無阻塞:沒有競爭,線程就能夠持續執行。

         2)無鎖:系統總體持續運行。

         3)無等待:每一個線程均可以執行,即便遇到競爭也是如此。只有極少算法(實現這一點)。

1.CAS操做

         一種基本的非阻塞算法叫作比較並交換(CAS),其包含三個數:內存位置(V)、預期原值(A)和新值(B)。若是內存位置的值與預期原值相同,那麼將該位置的值更新爲新值。

int test()
{
    int v;
    v = value.get();
    while(!value.compareAndSet(v, v+1));
    return v+1;
}

       上面是使用該方法的一個計數器例子:將一個變量更新爲新值,但若是從我上次看到這個變量以後其餘線程修改了它的值,那麼更新失敗,從新再來。

        在輕度和中度競爭狀況下,非阻塞算法的性能會超過阻塞算法,由於CAS的大多數時間在第一次嘗試時就成功,而競爭時開銷也不涉及線程掛起,只多了幾個循環迭代;在高度競爭狀況下,基於鎖的算法提供比非阻塞算法更好的吞吐率。

2.ABA問題

        在進行CAS操做時,主要看內存位置V的值A是否改變,但有一種狀況:內存位置V的值A先改變了成了B,又從B變回了A。這種狀況CAS算法便會產生問題。這類問題成爲ABA問題。

        要解決該類問題,一般將標記與要進行CAS操做的每一個值相關聯,並原子地更新值和標記。將ABA問題轉化爲ABA'。

3.cache乒乓現象

        若是將鎖競爭分佈到多個鎖上,而且每一個鎖可以保證在其線程完成操做以前沒有線程可以訪問它所保護的cache線,那麼它比等價的非阻塞算法性能要好。

 

5、openMP編程

1.openMP簡介

        openMP是一種面向共享內存以及分佈式共享內存的多處理器多線程編程語言。其執行模型爲fork-join形式:開始執行時,只有一個主線程運行,當遇到須要並行計算時,派生出fork來執行任務,任務結束後派生線程退出,只剩下主線程運行。

         該模型經過編譯製導語句來執行。編譯製導語句是指在編譯器編譯程序時,識別特定註釋。例如在C/C++語句中,用#pragma  omp  parallel來標誌後面一段爲並行程序塊。