---
# System prepended metadata

title: CSA final presentation - Clang Overview

---

# 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 僅是此項目中的一環（前端）而已！

![image](https://hackmd.io/_uploads/HkKV7hYE1e.png)

![image](https://hackmd.io/_uploads/HkAk4ntE1l.png)


## 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'
    ```
    ![image](https://hackmd.io/_uploads/r1EGfi9Eyg.png)


    明顯可見 clang 編譯速度高出 gcc 不少

- 比較兩個可執行檔的完成速度

    ```
    hyperfine --warmup 20 --runs 50 './matrix_mul_clang_O0' './matrix_mul_gcc_O0'
    ```
    ![image](https://hackmd.io/_uploads/rkEqWJq41x.png)

    執行檔的效能並無顯著差異。


**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'
    ```
    ![image](https://hackmd.io/_uploads/BJcJfJqE1x.png)

    明顯可見 clang 編譯速度高出 gcc 不少

- 比較兩個可執行檔的完成速度

    ```
    hyperfine --warmup 20 --runs 50 './array_sum_clang_O0' './array_sum_gcc_O0'
    ```
    ![image](https://hackmd.io/_uploads/SyeBz15Vke.png)

    執行檔的效能並無顯著差異。


## 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'
        ```
        ![image](https://hackmd.io/_uploads/BkFgQycEye.png)


        明顯可見 clang 編譯速度高出 gcc 不少
    
        ```
        hyperfine --warmup 20 --runs 50 './matrix_mul_clang_O1' './matrix_mul_gcc_O1'
        ```
        ![image](https://hackmd.io/_uploads/SJOIQkqVkl.png)

        在 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'
        ```
        ![image](https://hackmd.io/_uploads/B1t5Nkc41x.png)

        明顯可見 clang 編譯速度高出 gcc 不少

        ```
        hyperfine --warmup 20 --runs 50 './matrix_mul_clang_O2' './matrix_mul_gcc_O2'
        ```
        ![image](https://hackmd.io/_uploads/rk1RO1c4yg.png)

        在 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'
        ```
        ![image](https://hackmd.io/_uploads/ByeqKk5E1g.png)

        明顯可見 clang 編譯速度高出 gcc 不少
        
        ```
        hyperfine --warmup 20 --runs 50 './matrix_mul_clang_O3' './matrix_mul_gcc_O3'
        ```
        ![image](https://hackmd.io/_uploads/Hy0J9kqNyx.png)

        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'
        ```
        ![image](https://hackmd.io/_uploads/SJmd1l941l.png)

        明顯可見 clang 編譯速度高出 gcc 不少
    
        ```
        hyperfine --warmup 20 --runs 50 './array_sum_clang_O1' './array_sum_gcc_O1'
        ```
        ![image](https://hackmd.io/_uploads/B1dYXlq4Jg.png)

        執行檔的效能並無顯著差異。

    - -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'
        ```
        ![image](https://hackmd.io/_uploads/ry1zBx5VJx.png)

        明顯可見 clang 編譯速度高出 gcc 不少

        ```
        hyperfine --warmup 20 --runs 50 './array_sum_clang_O2' './array_sum_gcc_O2'
        ```
        ![image](https://hackmd.io/_uploads/ByxFrx9N1x.png)

        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'
        ```
        ![image](https://hackmd.io/_uploads/r1CQUxqVkx.png)


        明顯可見 clang 編譯速度高出 gcc 不少

        ```
        hyperfine --warmup 20 --runs 50 './array_sum_clang_O3' './array_sum_gcc_O3'
        ```
        ![image](https://hackmd.io/_uploads/S1bFIg54kg.png)

        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     |
    
- 檔案大小
    ![image](https://hackmd.io/_uploads/HkS51cqVkl.png)

    
    clang 均優於 gcc


## 7. Static Linking vs. Dynamic Linking
    
- -static: 靜態連結(正常都是dynamic liking)
    ![image](https://hackmd.io/_uploads/ByOZ8MpNyl.png)

    ```
    clang -O2 -static matrix_mul.c -o matrix_mul_clang_O2s
    ```
    
    ```
    clang -O2 -static array_sum.c -o array_sum_clang_O2s
    ```
    ![截圖 2024-12-14 下午1.13.45](https://hackmd.io/_uploads/S1Vt4c5V1e.png)

    觀察執行檔的組成
    
    ```
    size matrix_mul_clang_O2
    size matrix_mul_clang_O2s
    ```
    ![image](https://hackmd.io/_uploads/r1tkKc9Vyx.png)

## 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] 的值