###### tags: `PP` `交大`
# Assignment II: Multi-thread Programming
## Q1
### <font color="green">Produce a graph for VIEW1 and VIEW2 </font>
#### View1
```
$ ./mandelbrot -t 2
[mandelbrot serial]: [460.751] ms
Wrote image file mandelbrot-serial.ppm
[mandelbrot thread]: [236.919] ms
Wrote image file mandelbrot-thread.ppm
(1.94x speedup from 2 threads)
$ ./mandelbrot -t 3
[mandelbrot thread]: [282.714] ms
Wrote image file mandelbrot-thread.ppm
(1.63x speedup from 3 threads)
$ ./mandelbrot -t 4
[mandelbrot thread]: [193.399] ms
Wrote image file mandelbrot-thread.ppm
(2.38x speedup from 4 threads)
$ ./mandelbrot -t 8
[mandelbrot thread]: [135.141] ms
Wrote image file mandelbrot-thread.ppm
(3.41x speedup from 8 threads)
```

#### View2
```
$ ./mandelbrot -t 2 -v 2
[mandelbrot serial]: [270.723] ms
Wrote image file mandelbrot-serial.ppm
[mandelbrot thread]: [162.191] ms
Wrote image file mandelbrot-thread.ppm
(1.67x speedup from 2 threads)
$ ./mandelbrot -t 3 -v 2
[mandelbrot thread]: [124.378] ms
Wrote image file mandelbrot-thread.ppm
(2.18x speedup from 3 threads)
$ ./mandelbrot -t 4 -v 2
[mandelbrot thread]: [105.890] ms
Wrote image file mandelbrot-thread.ppm
(2.56x speedup from 4 threads)
$ ./mandelbrot -t 8 -v 2
[mandelbrot thread]: [74.189] ms
Wrote image file mandelbrot-thread.ppm
(3.65x speedup from 8 threads)
```

### <font color="green">Is speedup linear in the number of threads used?</font>
:::info
- **在 View1 中,speedup 並沒有成線性成長**,在 thread 數量為 3 時速度下降了。
- 在 View2 中,隨著thread數量上升,speedup 也隨之上升。
:::
### <font color="green">Why?</font>
推論的過程分成以下 3 步驟:
#### 1. 觀察 View1 與 View2 的不同之處

透過前個問題的結論,我們可以推測出 View1 與 View2 的不同之處很可能就是問題核心。
在仔細觀察兩張圖片後,可以發現 **View1 的淺色像素遠比 View2 來得多且集中**。在 8 位元灰階圖片中,0 表示黑色,最大值 255 則表示白色,數值越大顏色越淺。因此 View1 顯然比 View2 擁有更多數值較大的像素。
為了觀察此相異之處是否就是導致執行速度不同的主因,我進行了下步觀察。
#### 2. 觀察像素值是否與執行速度相關
```
static inline int mandel(float c_re, float c_im, int count)
{
int i;
for (i = 0; i < count; ++i)
{
if (z_re * z_re + z_im * z_im > 4.f) break;
...
}
return i;
}
```
上方程式碼為 `MandelbrotSerial.cpp` 中的 `mandel()` 函數,透過此函數可以看到每個像素被賦值的過程,並從中得出以下幾點:
* 像素值是由迴圈執行次數 `i` 來決定。
* `i` 可能為 0~255 中任一數 (因`count`為256),數值大小決定該像素顏色深淺。
* 綜合以上兩點可知,**一個像素顏色越淺(像素值越大),迴圈執行次數也越多**。
#### 3. 最終推論
* 我的程式會將圖片由上至下切成不同區塊,並按順序分配給不同 thread 執行,因此無法保證每個 thread 所負責的區域其淺色像素數量平均
* 使用 3 個 thread 時,分配給第 2 個 thread 的中間區塊有非常多的淺色像素,因此導致該 thread 執行速度拖垮整體速度
:::info
每個 thread 的工作量與其負責區域所含有的淺色像素數量有關。因此使用 3 個 thread 進行運算時,其中一個 thread 會被分配到中間白色最集中的區域,也因此導致了整體執行速度不佳。
:::
## Q2
### <font color="green">How do your measurements explain the speedup graph you previously created?</font>
以下三張圖片顯示在 thread 數量不同時,程式分配給不同 thread 的區塊各是哪區。
| 2 threads | 3 threads | 4 threads |
| :--------: | :--------: | :--------: |
|  |  |  |
以下為使用 timing code 來量測的 thread 平均執行時間
```
$ ./mandelbrot -t 2
thread 0 : 236.003 ms
thread 1 : 237.405 ms
[mandelbrot thread]: [236.942] ms
(1.94x speedup from 2 threads)
$ ./mandelbrot -t 3
thread 0 : 93.448 ms
thread 1 : 283.062 ms
thread 2 : 93.734 ms
[mandelbrot thread]: [282.980] ms
(1.63x speedup from 3 threads)
$ ./mandelbrot -t 4
thread 0 : 51.610 ms
thread 1 : 196.004 ms
thread 2 : 197.149 ms
thread 3 : 45.948 ms
[mandelbrot thread]: [193.496] ms
(2.38x speedup from 4 threads)
```
搭配圖片及各個 thread 實際的執行時間,可以觀察出:
* 2 threads : 2 個 thread 的工作量相當,花的時間也相近。
* 3 threads : 分配到中間區域的 thread 1 花了較多的執行時間。
* 4 threads : 分配到中間區域的 thread 1, 2 花了較多的執行時間。
實驗結果證實了我們在 Q1 的推測。
:::info
結論 : View1 在不同 thread 數量下的 speedup 之所以沒有成線性成長,是因每個 thread 工作量分配不均的關係。
:::
## Q3
### <font color="green">Modify the mapping of work to threads to achieve to improve speedup to at about 3-4x on both views</font>
```
#define ROWNUM 1
void workerThreadStart(WorkerArgs *const args) {
unsigned int gap = ROWNUM * args->numThreads;
for (unsigned int startRow = args->threadId * ROWNUM; startRow < args->height;
startRow += gap) {
mandelbrotSerial(args->x0, args->y0, args->x1, args->y1, args->width,
args->height, startRow, ROWNUM, args->maxIterations,
args->output);
}
}
```
### <font color="green">describe your approach to parallelization</font>
由於 View1 與 View2 的淺色區塊多集中,過去直接由上至下切分圖片的方法將會造成工作量不平均,因此重新更改每個 thread 負責的區域。以 3 個 thread 為例,新的分配方式如下圖顯示:

### <font color="green">report the final 4-thread speedup obtained</font>
:::info
View 1 : 3.79x speedup from 4 threads
View 2 : 3.78x speedup from 4 threads
:::
以下詳細列出在 View1, View2 分別使用 2, 3, 4個 thread 執行所得出的時間:
#### View 1
```
$ ./mandelbrot -t 2
[mandelbrot serial]: [460.828] ms
Wrote image file mandelbrot-serial.ppm
[mandelbrot thread]: [237.043] ms
Wrote image file mandelbrot-thread.ppm
(1.94x speedup from 2 threads)
$ ./mandelbrot -t 3
[mandelbrot thread]: [159.095] ms
Wrote image file mandelbrot-thread.ppm
(2.89x speedup from 3 threads)
$ ./mandelbrot -t 4
[mandelbrot thread]: [121.462] ms
Wrote image file mandelbrot-thread.ppm
(3.79x speedup from 4 threads)
```
#### View 2
```
$ ./mandelbrot -t 2 -v 2
[mandelbrot serial]: [288.194] ms
Wrote image file mandelbrot-serial.ppm
[mandelbrot thread]: [148.293] ms
Wrote image file mandelbrot-thread.ppm
(1.94x speedup from 2 threads)
$ ./mandelbrot -t 3 -v 2
[mandelbrot thread]: [99.637] ms
Wrote image file mandelbrot-thread.ppm
(2.89x speedup from 3 threads)
$ ./mandelbrot -t 4 -v 2
[mandelbrot thread]: [75.966] ms
Wrote image file mandelbrot-thread.ppm
(3.80x speedup from 4 threads)
```
## Q4
### <font color="green">Is performance with 8 threads greater than with 4 threads?</font>
#### View1
```
$ ./mandelbrot -t 8
[mandelbrot thread]: [126.211] ms
Wrote image file mandelbrot-thread.ppm
(3.65x speedup from 8 threads)
```
#### View2
```
$ ./mandelbrot -t 8 -v 2
[mandelbrot thread]: [76.074] ms
Wrote image file mandelbrot-thread.ppm
(3.79x speedup from 8 threads)
```
* View1 : 3.79x speedup from 4 threads, 3.65x speedup from 8 threads
* View2 : 3.78x speedup from 4 threads, 3.79x speedup from 8 threads
:::info
在 View 1 和 View 2 實驗中,使用 8 個 threads 得出的結果都會比使用 4 個 threads 略慢一些
:::
### <font color="green">Why?</font>
在執行 CPU-bound 程式時,thread 的數量應比核心數低才能盡量避免額外的 Context Switch Overhead。但我們卻在 4 核心的 PP Server 上使用了 8 個 thread,因此我認為這便是使用 8 threads 效能沒有顯著提昇的主要原因。
為了驗證這個猜想,我將程式執行於自己 8 核心的電腦上。執行結果如下,可以看出 8 threads 的執行效率比 4 threads 好了許多。
```
$ ./mandelbrot -t 4
[mandelbrot serial]: [383.379] ms
[mandelbrot thread]: [96.220] ms
(3.98x speedup from 4 threads)
$ ./mandelbrot -t 8
[mandelbrot thread]: [53.539] ms
(7.16x speedup from 8 threads)
```
:::info
當 thread 數量多於核心數,會造成額外的 context switch overhead。
因此使用 8 threads 的效能才會略差於 4 threads。
:::