---
# System prepended metadata

title: OS2020 HW1 - Benchmark Computer Black Box
tags: [os2020]

---


###### tags: `os2020`

# OS2020 HW1 - Benchmark Computer Black Box

> 學號：E14056431
> 姓名：趙珈葦
> 系級：機械109

## 開發環境

* OS: Ubuntu 16.04 LTS
* CPU: Intel® Core™ i7-6700 CPU @ 3.40GHz × 8 
* Memory: 16 GB
* Programming Language(version): C (GCC-5.5.0)

## 程式執行時間

1e8 筆資料排序(0.4G)：37.754134 秒
2e8 筆資料排序(0.8G)：87.050632 秒



## 程式開發與使用說明

### 開發功能

#### External Sorting
由於輸入資料可能大於記憶體容量，因此本支程式採用 External Sorting 進行排序。

簡單說明其原理，當檔案大小超過記憶體容量時，由於無法單次將所有內容寫入記憶體，因此先將原檔案拆分成數份記憶體可容納大小，依序把每一份讀入並排序完，再將其先寫入硬碟中儲存，待所有數據皆處理完成後再行合併。

#### Merge Sort
External Sort 中的單筆資料排序並沒有指定排序方式，考量若欲提高執行效率，單筆分割資料中的數據數量亦可能仍非常大，因此選擇時間複雜度 $O(nlogn)$ 的 Merge Sort。

#### K-Merge & Heap Sort
External Sort 最後的檔案合併以 K-Merge 演算法處理，即以多通道已排序數列，不斷取最小值更新目標數列來完成數列合併，程式中便是以各檔案的輸入取代上述已排序數列。

並且在未知記憶體上限及輸入資料數量的情況下，須合併的檔案數量可以非常大，因此仍須一有效率的演算法比較每個檔案最新輸入值的大小。程式中使用 Heap Sort，首先將各輸入第一數排序完成，取完最小數後讀取新值取代 root，並重新排序更新數列，重複動作至每一檔案讀完為止。

#### 執行流程圖：
```graphviz
digraph progress{
    rankdir=LR;
    input [label="input.txt" shape="box"];
    sort [label="Partition & Merge Sort"];
    "part_1.txt" [shape="box"]
    "part_2.txt" [shape="box"]
    "..." [shape="box"]
    merge [label="K-Merge"];
    output [label="output.txt" shape="box"];
    
    input -> sort;
    sort -> "part_1.txt";
    sort -> "part_2.txt";
    sort -> "...";
    "part_1.txt" -> merge;
    "part_2.txt" -> merge;
    "..." -> merge;
    merge -> output
}
```

### 執行方式

#### Build with `build.sh`
```bash
# At root directory of benchmark
$ bash ./build.sh

# It'll generate two executable files at current directory - sort & datagen
```

#### Build single program
```bash
# At root directory of benchmark

# Data generator
$ cd data_generator && make && cd ..

# Sorting program
$ cd sorting && make && cd ..
```

#### Implement
產生測試資料：
```bash
# Generate input data
$ ./datagen [number_of_data] [target_file]

# ex. 
# ./datagen 1000 input.txt
# It'll generate a file "input.txt" which contains 1000 random numbers
```

排序資料：
```bash
# Sort
$ ./sort [input_file]

# ex.
# ./sort input.txt
```

## 效能分析報告

### 單一執行工作最佳化

#### 檔案讀寫
由於輸入數據量龐大，且 External Sort 演算法的讀寫需求次數亦高，若選擇較具效率的方式執行應能降低一定程度的執行時間，因此在此針對檔案讀寫個別撰寫三種方式測試。

* 寫出：寫出 $5*10^6$ 個 32-bit 整數至一輸出檔 ([Github](https://github.com/chwchao/Course-OS2020/tree/master/benchmark/measurement/write_file))
    * `fprintf`：0.302281 秒
    * `sprintf` + `fputs`：380.977516 秒（1.3e6 * 8-bit char buffer）
    * `fwrite`：0.007645 秒 （1e6 * 32-bit int buffer）

    前兩項測試輸出檔為 txt 文字檔，可看出僅使用 `fprintf` 在此處效果較佳。
    論純字串寫入速度理應 `fputs` 較 `fprintf` 佳，但由於寫入內容為數字，須先經由 `sprintf` 轉換，反而拖慢整體速度，因此在程式最終輸出 `output.txt` 的實作使用 `fprintf` 輸出。
    
    而 `fwrite` 表現效率較其餘兩者高出許多，但輸出格式為 binary 檔，並不符合程式功能要求的文字格式，然而在 External Sort 中仍有分割檔案儲存的需求，此時並不限輸出格式，因此本實作利用 `fwrite` 作為中間運算的加速手法。

* 讀入：讀入 $5*10^6$ 個 32-bit 整數至一輸出檔 ([Github](https://github.com/chwchao/Course-OS2020/tree/master/benchmark/measurement/read_file))
    * `fscanf`：0.513792 秒
    * `fgets` + `atoi`：0.230732 秒
    * `fread`：0.003187 秒（ 1e6 * 32-bit int buffer）

    前兩項測試輸入檔為 txt 文字檔，由於 `fscanf` 內部做較多 formatting 處理，因此速度較慢，因此程式中使用 `fgets` 和 `atoi` 的組合實作輸入檔案的讀取。
    
    而 `fread` 為 binary 檔的讀取函式，速度最快，上述提到 External Sort 的分割檔案可以 `fwrite` 儲存為二進位檔加速，並可再利用此方式將值讀回，因此分割檔的讀取以 `fread` 進行實作。
    
### 整體效能觀察

此為單一執行緒的執行狀況 (perf)：

![](https://i.imgur.com/loUIlYM.png)

可以看到除此排序程式中的遞迴函式 `merge` 所佔比例最高，其餘占比較高的另有字串轉換數字 (`strtoll`) 及寫檔 (`fprintf` & `vfprintf`)，另外演算法中亦有 `heapify` 函式佔至 6%。

對於內容的讀寫在上方已經由測試程式驗證過，程式中已是使用較具效率的方式執行，這部份已是優化完結果，因此將優化目標著重於 `merge` ，即數據排序的動作。

在單一執行緒執行時，由於各分割檔是讀取完成、排序、最後儲存，然而若每個檔案內容在記憶體中儲存空間各自獨立，事實是可以同時進行處理的。因此程式被改寫為，當一個檔案中所需數量內容讀取完成後，該陣列將被送進額外執行緒，原執行緒則會再次重複動作。

由於使用裝置為八核心 CPU，因此程式中設定執行緒上限為 8，避免單核心同時處理多執行緒會造成效率降低，若同時 8 個執行緒皆工作中，主執行緒則會停等其一結束後再繼續執行。但目前的實驗結果中，由於單一 buffer 設定僅 125e6 個數字，數據處理過快導致程式最多只能同時跑 3 個執行緒，未來應可再針對這部分進行調整。

此圖為系統監控產生的 CPU 及記憶體運行狀況：

![](https://i.imgur.com/FKt7N79.png)