教育部智慧電子整峯性人才培育計畫 高階應岦處理器 AP...

Preview:

Citation preview

教育部智慧電子整合性人才培育計畫教育部智慧電子整合性人才培育計畫教育部智慧電子整合性人才培育計畫教育部智慧電子整合性人才培育計畫

高階應用處理器高階應用處理器高階應用處理器高階應用處理器 AP 聯盟中心聯盟中心聯盟中心聯盟中心

主題領域主題領域主題領域主題領域:::: 處理器軟硬體核心系統處理器軟硬體核心系統處理器軟硬體核心系統處理器軟硬體核心系統

課程模組名稱課程模組名稱課程模組名稱課程模組名稱: 編譯器實作模組編譯器實作模組編譯器實作模組編譯器實作模組

實驗模組名稱實驗模組名稱實驗模組名稱實驗模組名稱: 使用使用使用使用 LLVM 實作減少實作減少實作減少實作減少 OpenCL

程式分支分岐程式分支分岐程式分支分岐程式分支分岐(Branch

Divergence Reduction of

OpenCL Programs Using

LLVM)

開發教師開發教師開發教師開發教師: 游逸平游逸平游逸平游逸平

開發學生開發學生開發學生開發學生: 蔡也寧蔡也寧蔡也寧蔡也寧、、、、趙硯廷趙硯廷趙硯廷趙硯廷、、、、周昆霖周昆霖周昆霖周昆霖

學校系所學校系所學校系所學校系所: 國立交通大學資訊工程學系國立交通大學資訊工程學系國立交通大學資訊工程學系國立交通大學資訊工程學系

聯絡電話聯絡電話聯絡電話聯絡電話: 03-571-2121 轉轉轉轉 56688

聯絡地址聯絡地址聯絡地址聯絡地址: 新竹市大學路新竹市大學路新竹市大學路新竹市大學路 1001 號號號號

繳交日期繳交日期繳交日期繳交日期: 2014 年年年年 2 月月月月 1 日日日日

實驗內容關鍵字實驗內容關鍵字實驗內容關鍵字實驗內容關鍵字: LLVM, compiler backend,

optimization, GPGPU, OpenCL

1

Branch Divergence Reduction of

OpenCL Programs Using LLVM

實驗手冊實驗手冊實驗手冊實驗手冊-學生版學生版學生版學生版

【【【【前言前言前言前言】】】】

電腦科學發展至今,GPU 的運用也越來越重要,當然 CPU 與 GPU 這

種異質平台上的相互結合運用也更加受到重視而產生 OpenCL 這樣的程式

語言,OpenCL 的 code 分為 host code 與 kernel code,host code 由 CPU 負責

處理,而 kernel code 則由 GPU 負責。GPU 的運算所扮演的角色從過去處理

大部分的多媒體圖形資料到現在廣泛的被用來加速處理大量數值運算,越來

2

越多資料可以透過 GPU 的多核平行運算來提升運算效率並節省時間。但我

們發現 GPU 這種 SIMD(single instruction multiple data)處理資料的運算架構

常常因為 branch divergence 的問題浪費時間因而降低 GPU 的運算效能。

【【【【實驗問題與原理實驗問題與原理實驗問題與原理實驗問題與原理】】】】

Branch divergence 問題問題問題問題:

何謂 branch divergence 問題呢?

在GPU的設計架構下(以NVIDIA設計為例),一個GPU內有許多kernel,

而每個 kernel 裡頭有許多的執行緒(thread),32 個執行緒組成一個 warp,執

行緒在執行指令時以 warp 為一個執行單位,當遇到分支指令時(branch

instruction),由於每個 warp 裡頭的 thread 因為 data 不同,所以每個 thread

要執行的路徑也不一定相同,當有這種狀況發生時,warp 裡全部的執行緒

會先走過同一條路徑,之後再全部走過另一條分支路徑讓先前未執行指令的

執行緒再執行他們應該執行的指令,這也就是分支(divergence)的問題。

由圖 1 可得知,一個 warp 裡頭的執行緒在遇到 if_then_else 狀況時所產生的

branch divergence 問題,圖裡頭以灰線表示的執行緒即為未執行指令的執行

緒,所以我們可以盡量讓 if 和 else 裡頭的 code 量減少以降低這些未執行指

令的執行緒所浪費的時間。

圖 1

我們希望可以透過對 llvm 的 IR code 進行優化,讓程式碼在編譯階段就避免

掉 branch divergence 的問題,程式使用者即可以在不需要考慮 branch

divergence 的困擾下另外花費心思。

3

【【【【實驗目標實驗目標實驗目標實驗目標】】】】

我們使用 llvm 做為我們所要實驗的編譯器,llvm 以 clang 做為它的

front_end,經過 clang 後程式碼會變成 llvm 的 Intermediate Representation

(IR code),最後再經過 llvm 的 back_end 變成最終的 machine code。

而我們發現在利用 llvm 把 opencl 的 kernel code(kernel code 最後變成

GPU 執行檔)編譯成 IR code 時,如果沒有下任何的優化指令,llvm 的 IR code

有可能會出現一些 common subexpression 的程式碼結構,如下圖 2 表示:

圖 2

我們發現這樣的程式碼結構讓 GPU 裡頭的 thread 在執行指令時因為

branch divergence 的問題而浪費許多時間並降低效能,所以想辦法減少想辦法減少想辦法減少想辦法減少

branch 後路徑內的指令數目便可以大幅的改善後路徑內的指令數目便可以大幅的改善後路徑內的指令數目便可以大幅的改善後路徑內的指令數目便可以大幅的改善 thread 浪費時間去跑過而不浪費時間去跑過而不浪費時間去跑過而不浪費時間去跑過而不

執行那些沒意義的程式碼執行那些沒意義的程式碼執行那些沒意義的程式碼執行那些沒意義的程式碼,如此一來便可以提升 GPU 效能。

【【【【問題解決方法問題解決方法問題解決方法問題解決方法】】】】

在 llvm 的設計下,我們可以藉由在程式碼在 IR code 階段加入一個優化的

pass 來修改原始的 IR code。我可以再在這一個我們自製的 pass 裡頭實作我

們用來優化 IR code 的演算法,改變原始的 IR code 結構並且改善因為 branch

divergence 所降低的效能問題,下圖 3 即表示當一個由程式使用者完成的

OpenCL-kernel code 經由我們一連串的編譯與優化最後變成執行檔的過程:

圖 3

我們必須找出 IR code 裡全部有發生 branch divergence 的地方,並且想辦法

4

讓Branch後的兩條路徑內的 common sub expression code盡量減少,在 kernel

code 裡,只要是 if/if-then-else loop 的相關語法經過 clang 轉成 llvm IR code

之後,皆會出現 branch 問題,但能做 code 的搬移並且在顧慮到 code

dependency 的狀況下,我們發現只有 if-then-else 轉成的 IR code 才能實作,

因此我們必須找出是屬於 if-then-else 轉成的 IR code。

【【【【如何寫一個如何寫一個如何寫一個如何寫一個 pass】】】】

在寫一個 pass 之前,由於 LLVM opt 指令是操作在 LLVM IR 上,所以必須

先了解 LLVM IR 的架構 (參考附錄一)。

� 步驟一 : 寫一個 makefile

# Makefile for hello pass # Path to top level of LLVM hierarchy

LEVEL = ../../..

# Name of the library to build

LIBRARYNAME = Hello

# Make the shared library become a loadable module so the tools can

# dlopen/dlsym on the resulting library.

LOADABLE_MODULE = 1

# Include the makefile implementation stuff include

$(LEVEL)/Makefile.common

� 步驟二 : 寫一個 xxxx .cpp ,裡頭實作優化的演算法,由於要針對

LLVM IR 進行操作,所以必須符合 LLVM 所規定的形式與結構,因此

LLVM 提供許多 API 讓使用者使用,請參考以下連結以了解其 API 的使

用方式與功能。

LLVM API Documentation: http://llvm.org/doxygen/

� 步驟三: 編譯 xxxx .cpp => %make (makefile 必須和 xxxx.cpp 放在

同一個目錄底下)

Make 後會在 / YOUR LLVM DIRECTORY /Debug+Asserts/lib/產生一個

xxxx.so 檔

步驟四:利用 opt 指令以原本的 IR code 作為 input 並經過優化產生優化過

後的 IR codes

5

【【【【實驗流程說明實驗流程說明實驗流程說明實驗流程說明】】】】

首先需要一個 Unix-like 的 System 並在上面下載好 LLVM 和 Clang,並

且將兩者一起 build。再來需要 libclc,此 library 讓 compiler 可以認得 OpenCL

API。接下來我們將自己寫一個 optimization pass,透過 LLVM opt command

呼叫使用,來完成我們的優化。

※ opt 是一個模組化的 IR優化指令,透過opt指令並加上特定的參數與 flag

以 IR code 為 input 產生優化過的 IR code。

實驗流程圖實驗流程圖實驗流程圖實驗流程圖

6

【【【【實驗環境需求實驗環境需求實驗環境需求實驗環境需求】】】】

� Device :

���� Unix-like System

實驗環境可為 Mac OS,Fedora18,FreeBSD9 等,本實驗模組以

Ubuntu12.04.2 OS 為 OS 環境

���� Nvidia Display Driver

� Test Code Language:

���� OpenCL 語言語言語言語言

為實驗的測試語言,OpenCL 為一個結合異質平台運算的高階程式語言

參考網站 :http://www.khronos.org/opencl/

� Open Source :

���� LLVM

一個以 c++程式語言撰寫而成的虛擬編譯器,它是為了任意一種程式語

言寫成的程式,利用虛擬技術,創造出編譯時期,鏈結時期,執行時期

以及「閒置時期」的最佳化。參考網站: http://llvm.org/

���� clang

為本實驗模組負責編譯 host code 的程式語言,您也可以使用 gcc

���� libclc

為一個 OpenCL 的 library,LLVM 透過 link libclc 去編譯 OpenCL host

code 參考連結 : http://libclc.llvm.org/

7

【【【【環境環境環境環境&軟體設定軟體設定軟體設定軟體設定】】】】

◎◎◎◎ Open Source setup

1. 下載下載下載下載 LLVM

‧ svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm

2. 下載下載下載下載 clang

‧ cd llvm/tools

‧ svn co http://llvm.org/svn/llvm-project/cfe/trunk clang

3. Build LLVM & clang

‧ 當前目錄請在剛剛下載的 llvm 底下

‧ ./configure --enable-targets=nvptx

4. 下載下載下載下載 libclc

‧ svn checkout http://llvm.org/svn/llvm-project/libclc/trunk libclc

5. Build libclc with LLVM Config

‧ cd libclc

‧ ./configure.py --with-llvm-config =

/”YOUR LLVM DIRECTORY”/Debug+Asserts/bin/llvm-config

◎◎◎◎ Environment setup

1. Install OpenCL

‧ 安裝 NVIDIA CUDA Toolkit & Display Driver

a. CUDA toolkit

b. CUDA Samples

c. NVIDIA Display Driver

2. Install the NVIDIA GPU Computing SDK

‧ Download the Computint SDK

‧ ~/NVIDIA_GPU_Computing_SDK directory OpenCL / to make

8

【【【【實驗步驟實驗步驟實驗步驟實驗步驟】】】】

步驟步驟步驟步驟 1(.cl -> .ll):將將將將 kernel code 轉換成轉換成轉換成轉換成 LLVM IR code

% / YOUR LLVM DIRECTORY /Debug+Asserts/bin/clang

\ -I/YOUR LIBCLC DIRECTORY/generic/include -include clc/clc.h

\ -Dcl_clang_storage_class_specifiers -target nvptx--nvidiacl

\ -Xclang -mlink-bitcode-file

\ -Xclang /YOUR LIBCLC DIRECTORY/built_libs/nvptx--nvidiacl.bc

\ -S -emit-llvm kernel.cl -o kernel.ll -O0

步驟步驟步驟步驟 2:在在在在 LLVM 裡頭加入優化演算法的裡頭加入優化演算法的裡頭加入優化演算法的裡頭加入優化演算法的 pass。。。。

=> a.寫一個 make file

b.寫一個用來執行優化演算法的.cpp 檔

c .make .cpp 黨

完成後會在 Debug+Asserts/lib/目錄下產生.so 檔

請參考: http://llvm.org/docs/WritingAnLLVMPass.html

步驟步驟步驟步驟 3(.ll -> .ll):利用利用利用利用 opt 指令與所加入指令與所加入指令與所加入指令與所加入 pass,,,,以以以以 LLVM IR 為為為為 input 經過優經過優經過優經過優

化並產生優化過的化並產生優化過的化並產生優化過的化並產生優化過的 LLVM IR。。。。

% / YOUR LLVM DIRECTORY /Debug+Asserts/bin/opt -load p.so -flag

\ kernel.ll -S –o kernel_opt.ll

步驟步驟步驟步驟 4:將優化過的將優化過的將優化過的將優化過的 IR 轉換成轉換成轉換成轉換成 ”nvptx” back-end machine code。。。。

% / YOUR LLVM DIRECTORY /Debug+Asserts/bin/llc -march=nvptx \

\ -mcpu=sm_20 kernel_opt.ll –o kernel_opt.ptx

步驟步驟步驟步驟 5:在在在在host code中以中以中以中以 createprogramfrombinary()這個這個這個這個 api將產生的將產生的將產生的將產生的nvptx

code 傳入傳入傳入傳入,,,,並以並以並以並以 LLVM 的的的的 llc 編譯編譯編譯編譯 host code 即可產即可產即可產即可產生可執行檔生可執行檔生可執行檔生可執行檔。。。。

% / YOUR LLVM DIRECTORY /Debug+Asserts/bin/clang++ host.cpp -o host \

\ -lOpenCL

【【【【附錄一附錄一附錄一附錄一】】】】LLVM IR

特色特色特色特色::::

� LLVM IR 是 Static Single Assignment (SSA) 的形式. 每個變數僅被賦值

一次。SSA 最主要的用途,是藉由簡化變數的特性,來進行簡化及改進

編譯器最佳化的結果

9

� 由於是 SSA 的形式,LLVM IR 中有無限個虛擬的暫存器。

結構結構結構結構::::

� Module:LLVM Program 包含多個 modules,其中 modules 包含多個

functions ,global variables ,symbol tables entries。

� Function:包含多個 Basic block

� Basic block:包含多行指令,以 Terminator Instruction 結尾。

Identifiers::::

� Global identifiers begin with @

� Local identifiers begin with %

� Have some reserved words like other language

Functions:

� LLVM function definition consist of the “define” keyword

� LLVM function declaration consist of the “declare” keyword

例子例子例子例子::::

○ Kernel.cl

○ Kernel.ll ( 非完整 IR )

▪ define void @vector_add_gpu ( …. )

__kernel void vector_add_gpu (__global const float* src_a,__global

const float* src_b,__global float* res,const int num)

{

const int idx = get_global_id(0);

if (idx < num)

res[idx] = src_a[idx] + src_b[idx];

else

res[idx] = src_a[idx] - src_b[idx];

}

10

→ 這邊可以看到有一個 Fuction Definition

▪ if.then:

▪ %add = fadd float %0, %1 ; %add 為 Local 變數

▪ %arrayidx2 = getelementptr inbounds float addrspace(1)* %res, i32 %call

▪ store float %add, float addrspace(1)* %arrayidx2, align 4, !tbaa !2

▪ br label %if.end

→ if.then 為一個 label,代表一個 Basic block 的開始。

→ br label %if.end,為 terminator instruction,Basic block 的結束。

11

Branch Divergence Reduction of

OpenCL Programs Using LLVM

實驗手冊實驗手冊實驗手冊實驗手冊-教師版教師版教師版教師版

【【【【前言前言前言前言】】】】

電腦科學發展至今,GPU 的運用也越來越重要,當然 CPU 與 GPU 這

種異質平台上的相互結合運用也更加受到重視而產生 OpenCL 這樣的程式

語言,OpenCL 的 code 分為 host code 與 kernel code,host code 由 CPU 負責

處理,而 kernel code 則由 GPU 負責。GPU 的運算所扮演的角色從過去處理

大部分的多媒體圖形資料到現在廣泛的被用來加速處理大量數值運算,越來

12

越多資料可以透過 GPU 的多核平行運算來提升運算效率並節省時間。但我

們發現 GPU 這種 SIMD(single instruction multiple data)處理資料的運算架構

常常因為 branch divergence 的問題浪費時間因而降低 GPU 的運算效能。

【【【【實驗問題與原理實驗問題與原理實驗問題與原理實驗問題與原理】】】】

Branch divergence 問題問題問題問題:

何謂 branch divergence 問題呢?

在GPU的設計架構下(以NVIDIA設計為例),一個GPU內有許多kernel,

而每個 kernel 裡頭有許多的執行緒(thread),32 個執行緒組成一個 warp,執

行緒在執行指令時以 warp 為一個執行單位,當遇到分支指令時(branch

instruction),由於每個 warp 裡頭的 thread 因為 data 不同,所以每個 thread

要執行的路徑也不一定相同,當有這種狀況發生時,warp 裡全部的執行緒

會先走過同一條路徑,之後再全部走過另一條分支路徑讓先前未執行指令的

執行緒再執行他們應該執行的指令,這也就是分支(divergence)的問題。

由圖 1 可得知,一個 warp 裡頭的執行緒在遇到 if_then_else 狀況時所產生的

branch divergence 問題,圖裡頭以灰線表示的執行緒即為未執行指令的執行

緒,所以我們可以盡量讓 if 和 else 裡頭的 code 量減少以降低這些未執行指

令的執行緒所浪費的時間。

圖 1

我們希望可以透過對 llvm 的 IR code 進行優化,讓程式碼在編譯階段就避免

掉 branch divergence 的問題,程式使用者即可以在不需要考慮 branch

divergence 的困擾下另外花費心思。

13

【【【【實驗目標實驗目標實驗目標實驗目標】】】】

我們使用 llvm 做為我們所要實驗的編譯器,llvm 以 clang 做為它的

front_end,經過 clang 後程式碼會變成 llvm 的 Intermediate Representation

(IR code),最後再經過 llvm 的 back_end 變成最終的 machine code。

而我們發現在利用 llvm 把 opencl 的 kernel code(kernel code 最後變成

GPU 執行檔)編譯成 IR code 時,如果沒有下任何的優化指令,llvm 的 IR code

有可能會出現一些 common subexpression 的程式碼結構,如下圖 2 表示:

圖 2

我們發現這樣的程式碼結構讓 GPU 裡頭的 thread 在執行指令時因為

branch divergence 的問題而浪費許多時間並降低效能,所以想辦法減少想辦法減少想辦法減少想辦法減少

branch 後路徑內的指令數目便可以大幅的改善後路徑內的指令數目便可以大幅的改善後路徑內的指令數目便可以大幅的改善後路徑內的指令數目便可以大幅的改善 thread 浪費時間去跑過而不浪費時間去跑過而不浪費時間去跑過而不浪費時間去跑過而不

執行那些沒意義的程式碼執行那些沒意義的程式碼執行那些沒意義的程式碼執行那些沒意義的程式碼,如此一來便可以提升 GPU 效能。

【【【【問題解決方法問題解決方法問題解決方法問題解決方法】】】】

在 llvm 的設計下,我們可以藉由在程式碼在 IR code 階段加入一個優化的

pass 來修改原始的 IR code。我可以再在這一個我們自製的 pass 裡頭實作

我們用來優化 IR code 的演算法,改變原始的 IR code 結構並且改善因為

branch divergence 所降低的效能問題,下圖 3 即表示當一個由程式使用者

完成的 OpenCL-kernel code 經由我們一連串的編譯與優化最後變成執行

檔的過程:

圖 3

14

我們必須找出 IR code 裡全部有發生 branch divergence 的地方,並且想辦法

讓 Branch 後的兩條路徑內的 common subexpression code 盡量減少,在 kernel

code 裡,只要是 if/if-then-else loop 的相關語法經過 clang 轉成 llvm IR code

之後,皆會出現 branch 問題,但能做 code 的搬移並且在顧慮到 code

dependency 的狀況下,我們發現只有 if-then-else 轉成的 IR code 才能實作,

因此我們必須找出是屬於 if-then-else 轉成的 IR code。

【【【【如何寫一個如何寫一個如何寫一個如何寫一個 pass】】】】

在寫一個 pass 之前,由於 LLVM opt 指令是操作在 LLVM IR 上,所以必須

先了解 LLVM IR 的架構 (參考附錄一)。

� 步驟一 : 寫一個 makefile

# Makefile for hello pass # Path to top level of LLVM hierarchy

LEVEL = ../../..

# Name of the library to build

LIBRARYNAME = Hello

# Make the shared library become a loadable module so the tools can

# dlopen/dlsym on the resulting library.

LOADABLE_MODULE = 1

# Include the makefile implementation stuff include

$(LEVEL)/Makefile.common

� 步驟二 : 寫一個 xxxx .cpp ,裡頭實作優化的演算法,由於要針對

LLVM IR 進行操作,所以必須符合 LLVM 所規定的形式與結構,因此

LLVM 提供許多 API 讓使用者使用,請參考以下連結以了解其 API 的使

用方式與功能。

LLVM API Documentation: http://llvm.org/doxygen/

� 步驟三: 編譯 xxxx .cpp => %make (makefile 必須和 xxxx.cpp 放在

同一個目錄底下)

Make 後會在 / YOUR LLVM DIRECTORY /Debug+Asserts/lib/產生一個

xxxx.so 檔

步驟四:利用 opt 指令以原本的 IR code 作為 input 並經過優化產生優化過

後的 IR codes

15

【【【【演算法實作原理演算法實作原理演算法實作原理演算法實作原理】】】】

步驟一步驟一步驟一步驟一 (Algorithm1):

找出 IR code 裡頭所有的 branch 指令並且避免掉 loop 和 unconditional

branch(only if part)的 branch 指令,之後我們即可掌握 branch 的所有路徑與

關係架構,由 branch 指令可以得知 then part 的 basic block 與 else part 的 basic

block,並且判斷這 2 個 block 是否已經被 trace 過,如果沒有即可進一步判

斷這兩個 block 裡面是否能進行優化的動作。

Algorithm1:branch instruction finder and filter

1 traced_set=0

2 trace all the IR instruction(I)

3 if (I belong to branch instruction)then

4 if (I is belong to if-then-else branch instruction)then

5 trace to both then block(bb_then) & else block(bb_else)

6 if (bb_then/bb_else have not been checked and they can do optimization) then

7 do code hoisting/code sinking

8 add bb_then/bb_else to traced_set

步驟二步驟二步驟二步驟二:

進到我們的 basic block 裡後,接下來就是必須判斷他們是否可以進行優化,

所以要檢查他們是否有 common subexpression code,有的話這裡必須分兩種

狀況分別處理 code hoisting 與 code sinking 的 code 搬移動作。

步驟三步驟三步驟三步驟三(Algorithm2):

code hoisting 的動作必須發生在指令不屬於 store 指令,而且 then 和 else 各

自的 instruction 的等號右手邊有著相同的 code part,如圖4所表示:

圖 4

16

此外,因為是往他們所共同的 predecessor 搬移,所以我們必須確保 block 裡

如果存在著 nested if-then-else 時,我們必須由最深層的 if-then-else block 往

外做,但由於 llvm IR basic block 的基本關係架構,所以我們只要利用遞迴

的方式把 code hoisting 放在它往更深層呼叫的函式後面,如此一來便可以確

保搬移時保持先後順序關係。

步驟四步驟四步驟四步驟四(Algorithm3):

code sinking 則與 code hoisting 剛好相反,除了必須滿足 then 和 else 各自的

instruction 的等號右手邊有著相同的 code part 之外,它必須是屬於 store 指

令,如圖 5 所示:

圖 5

因為 store 必須儲存值的運算結果,所以並不可以做往上提的動作,而且在

做 code sinking 時如果發生 nested if-then-else 的狀況時,因為是往下提至它

們的 common successor 所以必須從做外層往最深層內做下去,因此我們要把

這個動作放在遞迴函式呼叫的前面,以確保 store 之後能讓裡層的 basic block

看到。

Algorithm2: code hoisting

1 trace into inner if-then-else of (basic block)bb until the deepest if-then-else has

2 been found

3 while(not reach the most outer bb) do

4 if "IR instruction"(I) satisfy the following condition:

Common part on the right hand side of =

17

5 a. identical sequence of code on the right hand side of "=" of each

6 instruction of both then/else block

7 b. I is not store instruction

8 then

9 hoisting the code part of operation instruction(right value)

10 goto next outer bb

Algorithm3: code sinking

1 trace into from the most outer if-then-else of (basic block)bb

2 while(not reach the most inner if-then-else bb) do

3 if "IR instruction" satisfy the following condition:

4 a. store instruction

5 b. store to the same address

6 then

7 sinking the code part of store instruction(left value)

8 goto next inner bb

【【【【實驗流程說明實驗流程說明實驗流程說明實驗流程說明】】】】

首先需要一個 Unix-like 的 System 並在上面下載好 LLVM 和 Clang,並且將

兩者一起build。再來需要 libclc,此 library讓compiler可以認得OpenCL API。

接下來我們將自己寫一個 optimization pass,透過 LLVM opt command 呼叫

使用,來完成我們的優化。

※ opt 是一個模組化的 IR優化指令,透過opt指令並加上特定的參數與 flag

以 IR code 為 input 產生優化過的 IR code。

18

實驗流程圖實驗流程圖實驗流程圖實驗流程圖

【【【【實驗環境需求實驗環境需求實驗環境需求實驗環境需求】】】】

� Device :

���� Unix-like System

實驗環境可為 Mac OS,Fedora18,FreeBSD9 等,本實驗模組以

Ubuntu12.04.2 OS 為 OS 環境

���� Nvidia Display Driver

� Test Code Language:

19

���� OpenCL 語言語言語言語言

為實驗的測試語言,OpenCL 為一個結合異質平台運算的高階程式語言

參考網站 :http://www.khronos.org/opencl/

� Open Source :

���� LLVM

一個以 c++程式語言撰寫而成的虛擬編譯器,它是為了任意一種程式語

言寫成的程式,利用虛擬技術,創造出編譯時期,鏈結時期,執行時期

以及「閒置時期」的最佳化。參考網站: http://llvm.org/

���� clang

為本實驗模組負責編譯 host code 的程式語言,您也可以使用 gcc

���� libclc

為一個 OpenCL 的 library,LLVM 透過 link libclc 去編譯 OpenCL host

code 參考連結 : http://libclc.llvm.org/

【【【【環境環境環境環境&軟體設定軟體設定軟體設定軟體設定】】】】

◎◎◎◎ Open Source setup

6. 下載下載下載下載 LLVM

‧ svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm

7. 下載下載下載下載 clang

‧ cd llvm/tools

‧ svn co http://llvm.org/svn/llvm-project/cfe/trunk clang

8. Build LLVM & clang

‧ 當前目錄請在剛剛下載的 llvm 底下

‧ ./configure --enable-targets=nvptx

20

9. 下載下載下載下載 libclc

‧ svn checkout http://llvm.org/svn/llvm-project/libclc/trunk libclc

10. Build libclc with LLVM Config

‧ cd libclc

‧ ./configure.py --with-llvm-config =

/”YOUR LLVM DIRECTORY”/Debug+Asserts/bin/llvm-config

◎◎◎◎ Environment setup

1. Install OpenCL

‧ 安裝 NVIDIA CUDA Toolkit & Display Driver

a. CUDA toolkit

b. CUDA Samples

c. NVIDIA Display Driver

2. Install the NVIDIA GPU Computing SDK

‧ Download the Computint SDK

‧ ~/NVIDIA_GPU_Computing_SDK directory OpenCL / to make

【【【【實驗步驟實驗步驟實驗步驟實驗步驟】】】】

步驟步驟步驟步驟 1(.cl -> .ll):將將將將 kernel code 轉換成轉換成轉換成轉換成 LLVM IR code

% / YOUR LLVM DIRECTORY /Debug+Asserts/bin/clang

\ -I/YOUR LIBCLC DIRECTORY/generic/include -include clc/clc.h

\ -Dcl_clang_storage_class_specifiers -target nvptx--nvidiacl

\ -Xclang -mlink-bitcode-file

\ -Xclang /YOUR LIBCLC DIRECTORY/built_libs/nvptx--nvidiacl.bc

\ -S -emit-llvm kernel.cl -o kernel.ll -O0

步驟步驟步驟步驟 2:在在在在 LLVM 裡頭加入優化演算法的裡頭加入優化演算法的裡頭加入優化演算法的裡頭加入優化演算法的 pass。。。。

=> a.寫一個 make file

b.寫一個用來執行優化演算法的.cpp 檔

c .make .cpp 黨

完成後會在 Debug+Asserts/lib/目錄下產生.so 檔

請參考: http://llvm.org/docs/WritingAnLLVMPass.html

步驟步驟步驟步驟 3(.ll -> .ll):利用利用利用利用 opt 指令與所加入指令與所加入指令與所加入指令與所加入 pass,,,,以以以以 LLVM IR 為為為為 input 經過優經過優經過優經過優

21

化並產生優化過的化並產生優化過的化並產生優化過的化並產生優化過的 LLVM IR。。。。

% / YOUR LLVM DIRECTORY /Debug+Asserts/bin/opt -load p.so -flag

\ kernel.ll -S –o kernel_opt.ll

步驟步驟步驟步驟 4:將優化過的將優化過的將優化過的將優化過的 IR 轉換成轉換成轉換成轉換成 ”nvptx” back-end machine code。。。。

% / YOUR LLVM DIRECTORY /Debug+Asserts/bin/llc -march=nvptx \

\ -mcpu=sm_20 kernel_opt.ll –o kernel_opt.ptx

步驟步驟步驟步驟 5:在在在在host code中以中以中以中以 createprogramfrombinary()這個這個這個這個 api將產生的將產生的將產生的將產生的nvptx

code 傳入傳入傳入傳入,,,,並以並以並以並以 LLVM 的的的的 llc 編譯編譯編譯編譯 host code 即可產生可執行檔即可產生可執行檔即可產生可執行檔即可產生可執行檔。。。。

% / YOUR LLVM DIRECTORY /Debug+Asserts/bin/clang++ host.cpp -o host \

\ -lOpenCL

【【【【效能與成果效能與成果效能與成果效能與成果】】】】

測資測資測資測資 1‧‧‧‧矩陣運算矩陣運算矩陣運算矩陣運算

優化資訊:

Kernel 優化時間:Average 0.0001(sec) ~ 0.0002(sec)

Total times doing code hoisting – 13 times

Total times doing code sinking – 2 times

比較圖:

0

0.05

0.1

0.15

0.2

0.25

5000 10000 50000 100000 300000 500000 600000

毫毫毫毫

秒秒秒秒

matrix size

unoptimized

optimized

22

測資測資測資測資 2‧‧‧‧蒙地卡羅法求蒙地卡羅法求蒙地卡羅法求蒙地卡羅法求 pi 值值值值

優化資訊:

Kernel 優化時間:Average 0.0001(sec) ~ 0.0002(sec)

Total times doing code hoisting – 7 times

Total times doing code sinking – 1 times

比較圖:

【【【【附錄一附錄一附錄一附錄一】】】】LLVM IR

特色特色特色特色::::

� LLVM IR 是 Static Single Assignment (SSA) 的形式. 每個變數

0

0.02

0.04

0.06

0.08

0.1

0.12

0.14

0.16

0.18

5000 10000 50000 100000 300000 500000 600000

毫毫毫毫

秒秒秒秒

取樣個數取樣個數取樣個數取樣個數

unoptimized

optimized

23

僅被賦值一次。SSA 最主要的用途,是藉由簡化變數的特性,

來進行簡化及改進編譯器最佳化的結果

� 由於是 SSA 的形式,LLVM IR 中有無限個虛擬的暫存器。

結構結構結構結構::::

� Module:LLVM Program 包含多個 modules,其中 modules 包

含多個 functions ,global variables ,symbol tables entries。

� Function:包含多個 Basic block

� Basic block:包含多行指令,以 Terminator Instruction 結尾。

Identifiers::::

� Global identifiers begin with @

� Local identifiers begin with %

� Have some reserved words like other language

Functions:

� LLVM function definition consist of the “define” keyword

� LLVM function declaration consist of the “declare” keyword

例子例子例子例子::::

○ Kernel.cl

24

○ Kernel.ll ( 非完整 IR )

▪ define void @vector_add_gpu ( …. )

→ 這邊可以看到有一個 Fuction Definition

▪ if.then:

▪ %add = fadd float %0, %1 ; %add 為 Local 變數

▪ %arrayidx2 = getelementptr inbounds float addrspace(1)* %res, i32 %call

▪ store float %add, float addrspace(1)* %arrayidx2, align 4, !tbaa !2

▪ br label %if.end

→ if.then 為一個 label,代表一個 Basic block 的開始。

→ br label %if.end,為 terminator instruction,Basic block 的結束

__kernel void vector_add_gpu (__global const float* src_a,__global

const float* src_b,__global float* res,const int num)

{

const int idx = get_global_id(0);

if (idx < num)

res[idx] = src_a[idx] + src_b[idx];

else

res[idx] = src_a[idx] - src_b[idx];

}

Recommended