# Programming Assignment I: SIMD Programming
[ToC]
## Part 1 : Vectorizing Code Using Fake SIMD Intrinsics
:::info
++***Q1-1***++:
**Does the vector utilization increase, decrease or stay the same as `VECTOR_WIDTH` changes? Why?**
:::
++***ans:***++
Run `./myexp -s 10000` and sweep the vector width from 2, 4, 8, to 16
* `VECTOR_WIDTH` = 2
```=
Results matched with answer!
****************** Printing Vector Unit Statistics *******************
Vector Width: 2
Total Vector Instructions: 167727
Vector Utilization: 90.1%
Utilized Vector Lanes: 302405
Total Vector Lanes: 335454
************************ Result Verification *************************
ClampedExp Passed!!!
```
* `VECTOR_WIDTH` = 4
```=
Results matched with answer!
****************** Printing Vector Unit Statistics *******************
Vector Width: 4
Total Vector Instructions: 97075
Vector Utilization: 88.1%
Utilized Vector Lanes: 342041
Total Vector Lanes: 388300
************************ Result Verification *************************
ClampedExp Passed!!!
```
* `VECTOR_WIDTH` = 8
```=
Results matched with answer!
****************** Printing Vector Unit Statistics *******************
Vector Width: 8
Total Vector Instructions: 52877
Vector Utilization: 87.0%
Utilized Vector Lanes: 368081
Total Vector Lanes: 423016
************************ Result Verification *************************
ClampedExp Passed!!!
```
* `VECTOR_WIDTH` = 16
```=
Results matched with answer!
****************** Printing Vector Unit Statistics *******************
Vector Width: 16
Total Vector Instructions: 27592
Vector Utilization: 86.5%
Utilized Vector Lanes: 381929
Total Vector Lanes: 441472
************************ Result Verification *************************
ClampedExp Passed!!!
```
結果顯示,當`VECTOR_WIDTH`增加時,<font color=#FF0000>vector utilization</font>會降低。
++***why?***++
主要影響vector utilization的因素是mask的使用,而有以下兩個情況會需要透過mask來確保結果正確:
* **vector內各元素需要的運算量不同**
因為<font color=#FF0000>**每個元素要計算的指數是隨機給予的**</font>,在平行計算時會面臨到vector內的元素不會同時運算完畢的情況。此時便需要透過mask將較早運算完的結果保留下來,造成在有元素已經運算完畢,而其他元素尚未完成的情況下,vector utilization降低。
* **超過9.999999的結果要設為9.999999**
當結果都運算完後,便需要<font color=#FF0000>透過mask來將超過門檻的結果設為9.999999</font>。因此取決於此vector內的運算結果超過門檻的數量,也會使vector utilization降低。
結合以上兩點,**隨著vector長度上升,需要考慮的元素變多,互相等待、遮罩的情況會更嚴重,使得vector utilization更容易下降**。
:warning:但這是建立於通常情況,在某些例子下的結果可能會不一樣:warning:
## Part 2 : Vectorizing Code with Automatic Vectorization Optimizations
:::info
++***Q2-1***++:
* Fix the code to make sure it uses aligned moves for the best performance.
:::
++***observation***++:
從生成的assembly code中可以發現:
```=49
...
vmovups (%rbx,%rcx,4), %ymm0
vmovups 32(%rbx,%rcx,4), %ymm1
vmovups 64(%rbx,%rcx,4), %ymm2
vmovups 96(%rbx,%rcx,4), %ymm3
...
```
AVX2指令集中的registers **ymm**的對齊單位為**32 bits**,因此要透過`__builtin_assume_aligned()`函式告訴編譯器`a`,`b`,和`C`對齊的單位為32 bits。
因此在`test1.cpp`中加入以下程式碼:
```cpp=6
...
a = (float *)__builtin_assume_aligned(a, 32);
b = (float *)__builtin_assume_aligned(b, 32);
c = (float *)__builtin_assume_aligned(c, 32);
...
```
++***result***++:
`vmovups`變成`vmovaps`:
```=49
...
vmovaps (%rbx,%rcx,4), %ymm0
vmovaps 32(%rbx,%rcx,4), %ymm1
vmovaps 64(%rbx,%rcx,4), %ymm2
vmovaps 96(%rbx,%rcx,4), %ymm3
...
```
---
:::info
++***Q2-2***++:
* What speedup does the vectorized code achieve over the unvectorized code?
* What additional speedup does using `-mavx2` give (`AVX2=1` in the `Makefile`)?
* What can you infer about the bit width of the default vector registers on the PP machines?
* What about the bit width of the _AVX2_ vector registers?
:::
++***speedup experiment (N: 1024, I: 20000000)***++ :
| **round** | **Unvectorized** | **Vectorized** | **Vectorized + AVX2** |
| -----------:| ----------------:| --------------:| ---------------------:|
| 1 | 8.42196 | 2.67959 | 1.42745 |
| 2 | 8.56017 | 2.67295 | 1.42949 |
| 3 | 8.43915 | 2.7005 | 1.42868 |
| 4 | 8.40761 | 2.66324 | 1.43153 |
| 5 | 8.40766 | 2.71379 | 1.43363 |
| 6 | 8.57744 | 2.83703 | 1.42635 |
| 7 | 8.45745 | 2.67869 | 1.42816 |
| 8 | 8.39355 | 2.68008 | 1.43499 |
| 9 | 8.50639 | 2.67706 | 1.43428 |
| 10 | 8.40772 | 2.69791 | 1.43311 |
| 11 | 8.41674 | 2.67807 | 1.43306 |
| 12 | 8.38 | 2.6795 | 1.43202 |
| | | | |
| **Median** | 8.41935 | 2.679545 | 1.431775 |
| **Speedup** | **1x** | **3.142082x** | **5.880358x** |
++***bit width***++ :
* **Default Machine :**
在只做`VECTORIZE=1`時,由生成出的Assembly code可以發現以下段落:
```=
...
movups 16(%rbx,%rcx,4), %xmm0
movups 16(%r15,%rcx,4), %xmm1
addps %xmm0, %xmm1
movups %xmm1, 16(%r14,%rcx,4)
movups 32(%rbx,%rcx,4), %xmm0
movups 32(%r15,%rcx,4), %xmm1
addps %xmm0, %xmm1
...
```
可以發現在將資料搬移進register `xmm0`、`xmm1`時,搬移資料的間隔為**16 bytes**,也就是**128 bits**。
* **AVX2**
將`VECTORIZE=1 AVX2=1`加入後,觀察同樣一段Assembly code會發現變成:
```=
...
vmovups (%rbx,%rdx,4), %ymm0
vmovups 32(%rbx,%rdx,4), %ymm1
vmovups 64(%rbx,%rdx,4), %ymm2
vmovups 96(%rbx,%rdx,4), %ymm3
vaddps (%r15,%rdx,4), %ymm0, %ymm0
vaddps 32(%r15,%rdx,4), %ymm1, %ymm1
vaddps 64(%r15,%rdx,4), %ymm2, %ymm2
vaddps 96(%r15,%rdx,4), %ymm3, %ymm3
...
```
可以得知AVX2指令集使用的registers `ymm0`、`ymm1`系列,大小為**32 bytes**,也就是**256 bits**。
---
:::info
++***Q2-3***++:
* Provide a theory for why the compiler is generating dramatically different assembly.
:::
++***observation***++:
* instructions
* `movaps`:將src 的資料搬進dst
* `maxps` : 比較src和dst的資料,把大的放入dst
* task
`test2.cpp`要做的事情是 ++*比較`a`和`b`裡面的值大小,並將大的放入`c`中*++
先看==修改之後==,`if (b[j] > a[j]) c[j] = b[j]; else c[j] = a[j];`的部分可以用兩個`movaps`和`maxps`達成,不會有同一vector內要進行不同運算的問題,因此編譯器認定可以被向量化:
```asm=47
...
.LBB0_3: # Parent Loop BB0_2 Depth=1
# => This Inner Loop Header: Depth=2
movaps (%r15,%rcx,4), %xmm0
movaps 16(%r15,%rcx,4), %xmm1
maxps (%rbx,%rcx,4), %xmm0
maxps 16(%rbx,%rcx,4), %xmm1
movaps %xmm0, (%r14,%rcx,4)
movaps %xmm1, 16(%r14,%rcx,4)
movaps 32(%r15,%rcx,4), %xmm0
movaps 48(%r15,%rcx,4), %xmm1
maxps 32(%rbx,%rcx,4), %xmm0
maxps 48(%rbx,%rcx,4), %xmm1
movaps %xmm0, 32(%r14,%rcx,4)
movaps %xmm1, 48(%r14,%rcx,4)
addq $16, %rcx
cmpq $1024, %rcx # imm = 0x400
jne .LBB0_3
...
```
而在==未修改==的版本中,可以看到:
```cpp=14
...
for (int j = 0; j < N; j++)
{
/* max() */
c[j] = a[j];
if (b[j] > a[j])
c[j] = b[j];
}
...
```
因為`c`先被assign,而比較的是`a`和`b`,編譯器無法得知兩個指令之間的關聯性,因此不會使用`maxps`來進行這段工作。當一個向量內的元素中,有一部分比較的結果是`b>a`,另一部分是`a<=b`,便會出現不同的分支,導致此向量無法統一繼續進行相同的運算。故編譯器判定無法向量化。
由assembly code也可看出其並非vectorize後的結果,而是以sequence的方式進行運算:
```asm=53
...
.LBB0_11: # in Loop: Header=BB0_3 Depth=2
addq $4, %rcx
cmpq $1024, %rcx # imm = 0x400
je .LBB0_12
.LBB0_3: # Parent Loop BB0_2 Depth=1
# => This Inner Loop Header: Depth=2
movaps (%r15,%rcx,4), %xmm1
movaps %xmm1, (%rbx,%rcx,4)
movaps (%r14,%rcx,4), %xmm0
ucomiss %xmm1, %xmm0
ja .LBB0_4
...
```
---