程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> GPGPU OpenCL/CUDA 高性能編程的10大注意事項

GPGPU OpenCL/CUDA 高性能編程的10大注意事項

編輯:關於C++

1.展開循環

如果提前知道了循環的次數,可以進行循環展開,這樣省去了循環條件的比較次數。但是同時也不能使得kernel代碼太大。

循環展開代碼例子:

#include<iostream>
using namespace std;
    
int main(){
    int sum=0;
    for(int i=1;i<=100;i++){
        sum+=i;
    }
    
    sum=0;
    for(int i=1;i<=100;i=i+5){
        sum+=i;
        sum+=i+1;
        sum+=i+2;
        sum+=i+3;
        sum+=i+4;
    }
    return 0;
}

2.避免處理非標准化數字

OpenCL中非標准化數字,是指數值小於最小能表示的正常值。由於計算機的位數有限,表示數據的范圍和精度都不可能是無限的。(具體可以查看IEEE 754標准,http://zh.wikipedia.org/zh-cn/IEEE_754)

在OpenCL中使用非標准化數字,可能會出現“除0操作”,處理很耗時間。

如果在kernel中“除0”操作影響不大的話,可以在編譯選項中加入-cl-denorms-are-zero,如:

clBuildProgram(program, 0, NULL, "-cl-denorms-are-zero", NULL, NULL);

3.通過編譯器選項傳輸常量基本類型數據到kernel,而不是使用private memory

如果程序中需要給kernel 傳輸常量基本類型數據,最好是使用編譯器選項,比如宏定義。而不是,每個work-item都定義一個private memory變量。這樣編譯器在編譯時,會直接進行變量替換,不會定義新的變量,節省空間。

如下面代碼所示(Dmacro.cpp):

#include<stdio.h>
int main()
{
    int a=SIZE;
    printf("a=%d, SIZE=%d\n",a,SIZE);
    return 0;
}

編譯:

g++ -DSIZE=128 -o A Dmacro.cpp

4.如果共享不重要的話,保存一部分變量在private memory而不是local memory

work-item訪問private memory速度快於local memory,因此可以把一部分變量數據保存在private memory中。當然,當private memory容量滿時,GPU硬件會自動將數據轉存到local memory中。

5.訪問local memory應避免bank conflicts

local memory被組織為一個一個的只能被單獨訪問的bank,bank之間交叉存儲數據,以便連續的32bit被保存在連續的bank中。如下圖所示:

(1)如果多個work-item訪問連續的local memory數據,他們就能最大限度的實現並行讀寫。

(2)如果多個work-item訪問同一個bank中的數據,他們就必須順序執行,嚴重降低數據讀取的並行性。因此,要合理安排數據在local memory中的布局。

(3)特殊情況,如果一個wave/warp中的線程同時讀取一個local memory中的一個地址,這時將進行廣播,不屬於bank 沖突。

6.避免使用”%“操作

"%"操作在GPU或者其他OpenCL設備上需要大量的處理時間,如果可能的話盡量避免使用模操作。

7.kernel中重用(Reuse) private memory,為同一變量定義不同的宏

如果kernel中有兩個或者以上的private variable在代碼中使用(比如一個在代碼段A,一個在代碼段B中),但是他們可以被數值相同。

也就是當一個變量用作不同的目的時,為了避免代碼中的命名困惑,可以使用宏。在一個變量上定義不同的宏。

如下面代碼所示:

#include<stdio.h>
int main(){
    int i=4;
    #define EXP i
            printf("EXP=%d\n",EXP);
        
    #define COUNT i
            printf("COUNT=%d\n",COUNT);
    getchar();
    return 0;
}

8.對於(a*b+c)操作,盡量使用 fma function

如果定義了“FP_FAST_FMAF”宏,就可以使用函數fma(a,b,c)精確的計算a*b+c。函數fma(a,b,c)的執行時間小於或等於計算a*b+c。

9.在program file 文件中對非kernel的函數使用inline

inline修飾符告訴編譯器在調用inline函數的地方,使用函數體替換函數調用。雖然會使得編譯後的代碼占用memory增加,但是省去了函數調用時上下、函數調用棧的切換操作,節省時間。

10.避免分支預測懲罰,應該盡量使得條件判斷為真的可能性大

現代處理器一般都會進行“分支預測”,以便更好的提前“預取”下一條要執行的指令,使得“取指令、譯碼分析、執行、保存”盡可能的並行。

在“分支預測”出錯時,提前取到的指令,不是要執行的指令,就需要根據跳轉指令,進行重新取指令,就是“分支預測懲罰”。

看如下的代碼:

#include<stdio.h>
int main()
{
   int i=1;
   int b=0;
   if(i == 1)
           b=1;
    else
        b=0;
    return 1;
}

對應的匯編代碼:

(movl 賦值,cmpl 比較,jne 不等於跳轉,jmp 無條件跳轉)

從上面的匯編指令代碼看出,如果比較(<main+24>)結果相等,則執行<main+26>也就是比較指令的下一條指令,對應b=1順序執行;如果比較(<main+24>)結果不相等,則執行跳轉到<main+35>,不是順序執行。

當然,有的處理器可能會根據以往“順序執行”與“跳轉執行”的比例來進行分支預測,但是這也是需要積累的過程。況且並不是,每個處理器多能這樣只能。

本文:http://www.cnblogs.com/xudong-bupt/p/3630952.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved