HPC 領域其中一個重要研究方向是加速科學計算程式,包含計算流體力學(CFD)、分子動力學、氣候模擬等。
在進行科學計算時,可以使用預先編譯好的版本得出計算結果,但在 HPC 領域追求的是更短的執行時間,編譯器、library 版本的選擇、使用了哪些參數都可能影響效能。
因此在傳統 HPC 的科學計算應用中,通常會選擇從原始程式編譯(build from source)來提升效能。
以 Lab3-4 會用到的 LULESH 為例,在 4 核心 VM 上執行 -s 30:不最佳化(-O0)約 140 秒,加上 -O3 約 31 秒,再開啟 OpenMP(4 threads)約 14 秒。三者的計算結果完全相同,差異只在於建置方式。
<aside> <img src="/icons/report_gray.svg" alt="/icons/report_gray.svg" width="40px" />
安裝:從這裡開始需要 gcc。在 Debian 上,build-essential 會一次裝好 gcc、g++、make、binutils(objdump)以及 C library 的開發檔(含後面 static linking 需要的 libc.a),後面的 Compilation Process、Makefile 都會用到:
sudo apt update
sudo apt install -y build-essential
gcc --version
</aside>
先看一個最簡單的例子:寫好一份 C 程式 hello_world.c 後,執行以下指令就能得到結果。
$ gcc hello_world.c -o hello_world
$ ./hello_world
hello world!
第一行的 gcc 就是編譯器(compiler),它把人看得懂的 hello_world.c 轉成 CPU 可以執行的 hello_world,第二行才是真正執行程式。
編譯器(compiler)是一種程式,會在程式執行前,將整份以高階語言(如 C 語言)撰寫的原始程式碼(source code)一次性轉換為 CPU 可以直接執行的機器碼執行檔(executable file)。
之所以需要編譯器,是因為電腦 CPU 僅能理解由 0 與 1 組成的「低階機器指令」,但直接用機器指令寫程式既困難又容易出錯。為提升開發效率,人類發明了接近自然邏輯的「高階語言」,讓我們能專注於演算法設計;編譯器則是兩者之間的橋樑,解決了高階語言無法直接執行的問題,並兼顧開發效率與執行效能。
<aside> <img src="/icons/report_gray.svg" alt="/icons/report_gray.svg" width="40px" />
編譯器(compiler) V.S. 直譯器(interpreter)
編譯:程式碼會先經過編譯器,將程式碼全部編譯成機器語言,編譯完後(產生執行檔)再一次執行,執行效率較高,但修改程式碼後需重新編譯。
直譯:程式碼會透過直譯器一行一行的被執行,修改後馬上可以重新執行,但執行效率較低。
兩者的目的相同,都是讓 CPU 執行高階語言寫成的程式,差別在「什麼時候翻譯成機器碼」:編譯器在執行前一次翻完,直譯器在執行時邊讀邊執行。直覺上編譯器的效能優勢很大,但兩者各有取捨,可以從三個面向比較:
實務上兩者常常搭配使用:例如 Python 負責流程控制,真正耗時的計算交給 NumPy 底下用 C / Fortran 編譯好的函式庫;HPC application 的核心則幾乎都是編譯語言。此外,Java、PyPy 等採用 JIT(Just-In-Time)編譯,在執行時把常跑的部分編譯成機器碼,兩者的界線也不再那麼分明。

source: https://hackmd.io/@Co-E5uCjTiSXhCaEXduquA/ryepv8F7U9
</aside>
常見的 CPU 編譯器有以下 2 種: