# CSA final presentation - Clang Overview
## 大綱
1. clang 基本介紹
2. clang 的常用參數
3. 介紹本次使用之效能評估軟體 **hyperfine**。
4. 比較其與 gcc 的編譯速度和執行檔的執行時間。
5. 比較不同優化參數造成的結果
6. 分析
7. Static Linking vs. Dynamic Linking
8. **LLDB** Debugger
## 1. Clang 基本介紹
- FreeBSD 預設內建的編譯器
- 隸屬於 **LLVM** 項目
- LLVM : 模組化的開源編譯器「架構」,讓大家可以客制化設計編譯器
https://github.com/llvm/llvm-project
- Clang 僅是此項目中的一環(前端)而已!


## 2. Clang 的常用參數
:::info
Clang 的命令參數和 gcc 大多相同。
:::
- **基本編譯、連結**
| 功能 | clang | gcc |
|:----------------:|:--------------:|:------------:|
| 編譯 | clang x.c -o x | gcc x.c -o x |
| 印出詳細訊息 | -v | -v |
| 指定編譯版本 | -std=c++17 | -std=c++17 |
| 不使用標準函式庫 | -nostdlib | -nostdlib |
| 靜態連結 | -static | -static |
- **Debug 相關**(會產生 debug 用的符號表嵌入 binary file)
| 功能 | clang | gcc |
|:-----------------:|:-----:|:---:|
| 生成debug符號表 | -g | -g |
| 壓縮debug符號表 | -gz | -gz |
| 最小化debug符號表 | -gmlt | X |
- **優化相關**
:::warning
每個優化選項背後相當於開啟了非常多的編譯選項,就像一個總開關,可使用
"-Q --help =optimizers" 得知其開啟之所有選項。
:::
| 功能 | clang | gcc |
|:--------:|:------:|:------:|
| 無優化 | -O0 | -O0 |
| 基本優化 | -O1 | -O1 |
| 進階優化 | -O2 | -O2 |
| 高級優化 | -O3 | -O3 |
| 極限優化 | -Ofast | -Ofast |
| 大小優化 | -Os | -Os |
| 調試優化 | -Og | -Og |
:::danger
-Ofast 非常不穩定,因為其採用了一些比較激進的優化,例如忽略浮點數標準,和更寬鬆的語法檢查。(可能會讓不合法的語句過編譯,或是過度的調度語句順序,會直接造成程式崩潰或運行邏輯被改變)
:::
## 3. Hyperfine 介紹
- 命令行性能測試工具
- 會將每條指令運行多次,做平均值、標準差、組距等分析,並做直觀的比較
(指令1 比 指令2 快多少倍)。
- 使用方法:
- 基本測試
```
hyperfine '指令1' '指令2'
```
- 預熱(第一次運行時,cache內的資料都和此程式無關,所以會特別慢。)
```
hyperfine --warmup 5 --runs 10 '指令1' '指令2'
```
## 4. 和 gcc 進行效能比較
:::info
我將針對兩種不同的情境進行評估。一是需要大量計算的程式(矩陣乘法),二是會頻繁讀寫記憶體的程式(陣列填充)。
所有比較我都會進行 **20** 次預熱和 **50** 次執行。
:::
**1. matrix_mul.c**
```c=
#include <stdio.h>
#include <stdlib.h>
#define N 500
void matrixMultiply(int A[N][N], int B[N][N], int C[N][N]) {
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
C[i][j] = 0;
for (int k = 0; k < N; ++k) {
C[i][j] += A[i][k] * B[k][j];
}
}
}
}
int main() {
static int A[N][N], B[N][N], C[N][N];
// 初始化矩陣 A 和 B
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
A[i][j] = i*j % 1000;
B[i][j] = i*j % 100;
}
}
matrixMultiply(A, B, C);
// 輸出部分結果
printf("C[0][0] = %d\n", C[0][0]);
printf("C[%d][%d] = %d\n", N-1, N-1, C[N-1][N-1]);
return 0;
}
```
- 此處我將不進行任何優化,觀察兩編譯器的原始編譯性能
```
hyperfine --warmup 20 --runs 50 'clang -O0 matrix_mul.c -o matrix_mul_clang_O0' 'gcc -O0 matrix_mul.c -o matrix_mul_gcc_O0'
```

明顯可見 clang 編譯速度高出 gcc 不少
- 比較兩個可執行檔的完成速度
```
hyperfine --warmup 20 --runs 50 './matrix_mul_clang_O0' './matrix_mul_gcc_O0'
```

執行檔的效能並無顯著差異。
**2. array_sum.c**
```c=
#include <stdio.h>
#include <stdlib.h>
#define N 10000000
int main() {
static int data[N];
long long sum = 0;
// 初始化陣列
for (int i = 0; i < N; ++i) {
data[i] = i % 100;
}
// 求和
for (int i = 0; i < N; ++i) {
sum += data[i];
}
printf("Sum = %lld\n", sum);
return 0;
}
```
- 此處我將不進行任何優化,觀察兩編譯器的原始編譯性能
```
hyperfine --warmup 20 --runs 50 'clang -O0 array_sum.c -o array_sum_clang_O0' 'gcc -O0 array_sum.c -o array_sum_gcc_O0'
```

明顯可見 clang 編譯速度高出 gcc 不少
- 比較兩個可執行檔的完成速度
```
hyperfine --warmup 20 --runs 50 './array_sum_clang_O0' './array_sum_gcc_O0'
```

執行檔的效能並無顯著差異。
## 5. 加入編譯優化
- 矩陣乘法
- -O1
```
hyperfine --warmup 20 --runs 50 'clang -O1 matrix_mul.c -o matrix_mul_clang_O1' 'gcc -O1 matrix_mul.c -o matrix_mul_gcc_O1'
```

明顯可見 clang 編譯速度高出 gcc 不少
```
hyperfine --warmup 20 --runs 50 './matrix_mul_clang_O1' './matrix_mul_gcc_O1'
```

在 O1 優化下, clang 的 **矩陣乘法** 效能完全海放 gcc 。
- -O2
```
hyperfine --warmup 20 --runs 50 'clang -O2 matrix_mul.c -o matrix_mul_clang_O2' 'gcc -O2 matrix_mul.c -o matrix_mul_gcc_O2'
```

明顯可見 clang 編譯速度高出 gcc 不少
```
hyperfine --warmup 20 --runs 50 './matrix_mul_clang_O2' './matrix_mul_gcc_O2'
```

在 O2 優化下, gcc 產生的執行檔效能顯著提升,已接近 clang 。
- -O3
```
hyperfine --warmup 20 --runs 50 'clang -O3 matrix_mul.c -o matrix_mul_clang_O3' 'gcc -O3 matrix_mul.c -o matrix_mul_gcc_O3'
```

明顯可見 clang 編譯速度高出 gcc 不少
```
hyperfine --warmup 20 --runs 50 './matrix_mul_clang_O3' './matrix_mul_gcc_O3'
```

O3 優化與 O2 並沒有明顯差別。
- 陣列讀寫
- -O1
```
hyperfine --warmup 20 --runs 50 'clang -O1 array_sum.c -o array_sum_clang_O1' 'gcc -O1 array_sum.c -o array_sum_gcc_O1'
```

明顯可見 clang 編譯速度高出 gcc 不少
```
hyperfine --warmup 20 --runs 50 './array_sum_clang_O1' './array_sum_gcc_O1'
```

執行檔的效能並無顯著差異。
- -O2
```
hyperfine --warmup 20 --runs 50 'clang -O2 array_sum.c -o array_sum_clang_O2' 'gcc -O2 array_sum.c -o array_sum_gcc_O2'
```

明顯可見 clang 編譯速度高出 gcc 不少
```
hyperfine --warmup 20 --runs 50 './array_sum_clang_O2' './array_sum_gcc_O2'
```

clang 的執行檔相比 O1 效能有進步,gcc 幾乎沒有。
- -O3
```
hyperfine --warmup 20 --runs 50 'clang -O3 array_sum.c -o array_sum_clang_O3' 'gcc -O3 array_sum.c -o array_sum_gcc_O3'
```

明顯可見 clang 編譯速度高出 gcc 不少
```
hyperfine --warmup 20 --runs 50 './array_sum_clang_O3' './array_sum_gcc_O3'
```

gcc 的執行檔相比 O2 效能有進步,clang 幾乎沒有。
## 6. 分析
用表格分析兩種編譯器的表現
- 平均編譯時長
- 矩陣乘法
| 優化參數 | clang | gcc | clang / gcc |
|:--------:|:-----:|:----:|:-----------:|
| -O0 | 11.1 | 24.4 | 2.20 |
| -O1 | 15.1 | 34.1 | 2.26 |
| -O2 | 17.2 | 32.7 | 1.89 |
| -O3 | 16.9 | 36.5 | 2.16 |
- 陣列讀寫
| 優化參數 | clang | gcc | clang / gcc |
|:--------:|:-----:|:----:|:-----------:|
| -O0 | 10.0 | 24.1 | 2.42 |
| -O1 | 13.2 | 25.3 | 1.91 |
| -O2 | 14.0 | 26.9 | 1.92 |
| -O3 | 14.6 | 30.7 | 2.10 |
clang 在所有測試項目中都超越 gcc 將近兩倍的效率。
- 平均運行時間
- 矩陣乘法
| 優化參數 | clang | gcc | clang / gcc |
|:--------:|:-----:|:-----:|:-----------:|
| -O0 | 280.1 | 284.4 | 1.02 |
| -O1 | 46.0 | 268.9 | 5.84 |
| -O2 | 37.3 | 42.8 | 1.15 |
| -O3 | 36.6 | 42.6 | 1.17 |
- 陣列讀寫
| 優化參數 | clang | gcc | clang / gcc |
|:--------:|:-----:|:----:|:-----------:|
| -O0 | 42.4 | 42.7 | 1.01 |
| -O1 | 9.5 | 9.5 | 1.01 |
| -O2 | 6.4 | 9.0 | 1.39 |
| -O3 | 6.4 | 7.6 | 1.18 |
- 檔案大小

clang 均優於 gcc
## 7. Static Linking vs. Dynamic Linking
- -static: 靜態連結(正常都是dynamic liking)

```
clang -O2 -static matrix_mul.c -o matrix_mul_clang_O2s
```
```
clang -O2 -static array_sum.c -o array_sum_clang_O2s
```

觀察執行檔的組成
```
size matrix_mul_clang_O2
size matrix_mul_clang_O2s
```

## 8. Debugger
- Clang 所搭配使用的 debugger 為 **LLDB**
- 記得在編譯時加上 ```-g``` ,否則 debugger 若沒有可查看的符號表,除錯會變得非常困難(看不到變數名、函數名、不能設斷點)。
```
clang -g matrix_mul.c -o matrix_mul_clang_g
```
- 編譯優化可能會造成非常多的 code 被重構,可能沒辦法設定斷點。
- 開始除錯吧
```
lldb ./matrix_mul_clang_g
```
- 常用指令
- ```r``` : 執行
- ```c``` : 繼續執行
- ```b 14``` : 在 14 行設定斷點
- ```b``` : 印出目前設定的所有斷點
- ```breakpoint delete``` : 刪除所有斷點
- ```p C[0][0]``` : 印出目前變數 C[0][0] 的值