# 2020_OS_Fall_HW2: ETL process
###### tags: `Operating Systems 2020`
:::info
**繳交期限:10/05-10/26 13:00**
:::
**作業目標**
- 請撰寫一支以多執行緒(Multi-Thread)開發的轉換程式,其功能是將CSV檔案轉換並輸出成JSON檔案。
- 請觀察及分析程式執行期間,包括但不限於CPU、Memory、Disk I/O的使用情況,探討作業系統是如何服務我們的程式。
___
**學號**:F74071069
**姓名**:林霆寬
**系級**:資訊111
### 開發環境
- OS: 虛擬機 Ubuntu 20.04
- CPU: Intel® Core™ i5-8250U CPU @ 1.6GHz × 6
- Memory: 4GB
- Programming Language(version): C89 (ISO/IEC 9899)
### 程式執行時間
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| -------- | -------- | -------- | -------- | -------- | -------- | -------- |:-------- |
| 90 | 83 | 78 | 78 | 82 | 77 | 75 | 76 |
| 97 | 97 | 86 | 78 | 84 | 78 | 76 | 77 |
| 101 | 84 | 76 | 76 | 82 | 86 | 76 | 69 |
| 100 | 83 | 78 | 72 | 81 | 77 | 66 | 79 |
| 99 | 85 | 80 | 73 | 74 | 75 | 79 | 77 |
| **97.4** | **84.2** | **78.0** | **76.6** | **79.4** | **78.6** | **74.4** | **75.6** |
▲ 表(一) 執行緒個數從 1 ~ 8 分別執行5次平均的時間,單位為秒
以上測試資料採用的大小約為 1.5 GB。這邊採用的是 real time,因為程式比較大,直接使用C語言的time(),精度到秒。
### 程式開發與使用說明
#### 程式開發流程
1. 先寫一支 single thread 程式,確保能夠正確執行並且結果正確。
2. Open 兩個 file 分別為 input.csv 和 output.json。然後宣告 char** 並 malloc 給他 DATA_SIZE 個 sizeof(char*) 的空間給它。接著用 fgets 一次從 input file 輸入一行直至檔案尾,同時每讀到一行就給 data[num] malloc BUF_SIZE 個 sizeof(char) 的空間,把讀進來的資料放進data[num],num++。此方式可以避免一次 malloc 太大的連續空間會要不到。(輸入完後 num 即為 輸入的行數。)
3. 接著用 sscanf 從 data[num] parse 並暫存 20 個 int,然後用 sprintf 將暫存的 int 依照 json 格式放回 data[num]。完成 num 次後輸出到 output file,完成 single thread 程式。
4. 改成 multi-thread 程式,這裡真正執行 multi-thread 是第 3 點的部分。假設有 n 個 thread,則將 num 組數據分給 n 個 thread,每個 thread 的工作內容就是像第 3 點一樣透過 sscanf、sprintf 轉換 data,也就是用 multi-thread 進行 data parallelism。至於細部的分法請見以下說明。
**分法:**
假設 num 也就是行數為 50,thread 個數為 4。
這邊定義兩個整數 low、high,利用取下界不取上界來分,也就是 [low, high)
第 i 個 thread (i from 0 to num - 1)
low = $\lfloor50\ \times\ \dfrac{i}{4}\rfloor$
high = $\lfloor50\ \times\ \dfrac{i + 1}{4}\rfloor$
最後拆分結果如下:
no1: 0 ~ 12
no2: 12 ~ 25
no3: 25 ~ 37
no4: 37 ~ 50
#### 使用說明
```bash
# Compile
# gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
$ gcc -pthread -o csv2json csv2json.c
# Run
$ ./csv2json [threads]
```
### 效能分析報告
#### 執行時間觀察
根據 表(一),可以畫出執行時間對不同執行時間的分布圖

▲ 圖(一) 不同執行緒5次平均執行時間分布,x軸為執行緒個數,y軸為平均執行時間(秒)
透過上圖,可以觀察到單執行緒的執行時間最久,隨著執行緒增加,執行時間慢慢降低,到了執行緒 4 個以上,變化幅度就沒那麼明顯,大約落在75 ~ 80 秒之間。
#### CPU使用率觀察
我先用以下指令記錄 top 中有關 CPU 和 MEM 的使用率到 record.txt。
```bash
$ top -b -d 0.5 -U <Username> > record.txt
```
並用 C++ 將想要的資料擷取下來並另存於 table.txt ,再用 Excel 整理成以下幾個圖表。
:::spoiler 詳細資訊 C++ 擷取
```c=
#include <fstream>
#include <iostream>
#include <string>
#include <cstdio>
using namespace std;
int main(void){
fstream fin;
FILE *fout;
fin.open("record.txt", ios::in);
fout = fopen("table.txt", "w+");
string s;
char a[20], c[20], m[20], p[20];
while (getline(fin, s)) {
size_t found = s.find("csv2json");
if (found != std::string::npos) {
sscanf(s.c_str(), "%s %s %s %s %s %s %s %s %s %s %s %s", p, a, a, a, a, a, a, a, c, m, a, a);
fprintf(fout, "%s %s %s\n", p, c, m);
}
}
fin.close();
fclose(fout);
return 0;
}
```
:::
<br>
以下資料為執行緒 1、2、4、6、8 分別執行一次的結果,測試資料均為 1.5 GB,圖表 x軸 時間單位以單執行緒執行時間作為 x軸 上界。

▲ 圖(二) 不同執行緒隨時間的CPU使用率變化
本次分析虛擬機配置 6核 (6緒),從上圖中可以觀察執行時間大致為 1緒 > 2緒 > 8緒 > 4緒 > 6緒,而 1緒 CPU 使用率約為 100%,2緒 CPU 使用率約為200%,4緒 CPU 使用率約為400%,6 及 8 緒 CPU 使用率約為600%。1、2 緒因為 CPU 使用率較低,在資料轉換的計算上比較費時,所以執行時間長。4、6 緒因為 CPU 使用率高,data parallelism 平行化大,圖中 CPU 使用率高起來的地方代表資料轉換的 stage,可以明顯觀察到黃線、綠線"ㄇ字型"的寬度比紅線、橘線小,代表轉換這個 stage 4、6 緒所花的時間比較少。至於 8緒,可以觀察到 CPU 使用率依然約為 600%,可以驗證程式的 thread 為虛擬 thread ,實際上實體 thread 還是要看作業系統如何分配。
#### MEM使用率觀察

▲ 圖(三) 不同執行緒隨時間的MEM使用率變化
本次作業程式部分是採用測試資料多大,就 malloc 多大空間的設計,所以不同執行緒讀取的 stage 都是呈現慢慢增加,到了高點後,MEM 使用率不再增加,代表讀完檔案,可見不同執行緒讀檔階段用時差不多,因為真正creat thread 是在轉換階段。
#### CPU、MEM 疊圖觀察

▲ 圖(四) 單執行緒隨時間的CPU、MEM使用率變化
從圖中可以很明顯觀察程式執行的三個時期
- 讀取時期
MEM 使用率慢慢增加到高點
- 轉換時期
當 MEM 使用率到高點時,CPU 使用率突然竄高並維持在高點
- 寫入時期
轉換完後,計算量降低,CPU使用率降低
#### 單獨看轉換時期
既然知道程式中只有轉換時期 creat thread,那就單獨分析轉換時期不同執行緒的執行時間。
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| -------- | -------- | -------- | ------- | ------- | ------- | ------- |:------- |
| 32 | 16 | 11 | 9 | 9 | 8 | 8 | 9 |
| 32 | 16 | 12 | 9 | 14 | 8 | 8 | 9 |
| 32 | 16 | 11 | 9 | 8 | 8 | 8 | 8 |
| 32 | 16 | 12 | 9 | 8 | 9 | 8 | 9 |
| 32 | 17 | 11 | 9 | 8 | 8 | 8 | 8 |
| **32.0** | **16.2** | **11.4** | **9.0** | **9.4** | **8.2** | **8.0** | **8.6** |
▲ 表(二) 執行緒個數從 1 ~ 8 分別執行5次平均的轉換時間,單位為秒
從上表中可以觀察到不同執行緒在轉換時期的執行時間還是有落差,2緒時間約為 1緒的一半,4緒的時間也約為 2緒的一半,而 4緒以上轉換時間差不多。
#### 結論
以我的虛擬機 6核 (6緒) 做總結,當程式 creat 的虛擬 thread $\leq\ 6$ 時,作業系統分配實體 thread 的數量會約等於程式 creat 的虛擬 thread 的數量,從 圖(二) 可以看出來,開多少 threads,CPU 使用率大約就是 100% × thread 個數,所以執行時間會隨著 thread 個數增加而縮短。至於程式 creat 的虛擬 thread > 6 時,作業系統也只能分配最多 6緒 給該程式,所以執行時間不會因此減少。