在計算機裡頭實踐演算法
主講人: jserv / 課程討論區: 2016 年系統軟體課程
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
返回「進階電腦系統理論與實作」課程進度表
「真正的發現之旅不在於尋求新的風景,而是在於擁有新的眼光。」
“The real voyage of discovery consists not in seeking new landscapes, but in having new eyes.”
― 法國作家 Marcel Proust
21 世紀的變革
[ source ]
- 1990 年代中期,大型 NUMA 平行計算系統架構曾發展極盛,但軟體的成熟度與研發速度跟不上系統挾摩爾定律前進的速度,剛好 x86 架構 PC 的效能、Linux 作業系統的泛用性也逼近當時的專業工作站、伺服器,而且二者相乘之後以價廉物美、超多人在用的優勢,讓 HPC 和 datacenter 的趨勢由 NUMA 轉為用許多廉價 PC 等級的機器相串聯而成的 cluster-based 大型系統。
- 事隔 20 年,NUMA 是否會再度興起呢?我認為在那些需要對 big data(或是過程中會產生big data)快速即時做錯綜複雜(交叉)分析的高價值應用而言,NUMA 是達到最高成就的必然之路。只是說,NUMA 其中的 processor-memory interconnect 以及 memory access protocol,比 20 年前有更多樣化的選擇;所謂的 shared memory 系統的定義,也可放寬,例如用 RDMA;而且 GPU、Vector、FPGA 等加速器也應該被涵蓋進來,這和我們探討過的 HSA 相關。
利用歐幾里得法 (也就是「輾轉相除法」) 找 GCD
(最大公因數; Greatest Common Divisor)
演算法:
- 大數 := 大數 - 小數
- 若「最大」和「最小」兩數相同,那麼它就是最大公因數,否則,回到第 1 步
示範:
GCD(48, 40)
用 C 語言實現歐幾里得法: (v1)
以 Intel C compiler 產生的機械碼來說: (Pentium 4)
指令 |
數量 |
延遲 |
cycle count |
減法 |
5 |
1 |
5 |
比較 |
5 |
1 |
5 |
分支 |
14 |
1 |
14 |
其他 |
0 |
1 |
0 |
|
|
|
24 |
改成 mod 操作的 C 語言程式如下: (v2)
以 Intel C compiler 產生的機械碼來說:
指令 |
數量 |
延遲 |
cycle count |
mod (整數除法) |
2 |
68 |
136 |
比較 |
3 |
1 |
3 |
分支 |
3 |
1 |
3 |
其他 |
6 |
1 |
6 |
|
|
|
148 |
如果可排除整數除法和減法的負擔,得到以下實做: (v3)
可得到更快的實做:
在 ARM Cortex-A8 上的實驗
-
實驗人: 張家榮
-
平台:BeagleBone Black (ARMv7-A, 1 GHz)
-
實驗程式碼:github
-
採用組合語言撰寫 (GCD 的部份),由於 Cortex-A8 不支援 idiv (要在 ARM Cortex-A15 以上才有),所以 以 ARM 組合語言實做計算機組織課本上的除法器的空間優化版本。
- 發現gcc的除法似乎有針對2的倍數做判斷,移位(除法)
- 實做的版本尚未考慮exception
實驗方法
- 圖上一個點,代表GCD (big, small) 中的 big,會尋找比它小的所有正整數(>1)的GCD所需的時間「總和」
- 如果有個 big=9995,那麼會找 9993 組 GCD,small 的範圍從 2 到 9994
- 如: (9995,2), (9995,3), (9995,4),…, (9995,9994)
- 所以big越大,找尋的組數越多,整體趨勢微微上升(從左到右)是正常的
- 重點是比較各演算法的趨勢
ARM vs. Thumb instruction
- ARM instruction速度較快
- 直線為curve fitting的結果(時間越低,效能越高)
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
Assembly
Euclidean(thumb) V.S. mod(thumb) V.S. mod_minus(thumb)
- 不符合預期的原因推估
- v3版本的branch過多(if,else),thumb的branch效能不佳,導致拉低平均

Euclidean(arm) V.S. mod(arm) V.S. mod_minus(arm)
- 比較
- 與thumb版本
- arm的branch效能較佳,所以v3版本速度改善最多,這裡符合預期
- v1輸給了v2
- arm的v1與thumb的v1相比,只有微微速度提昇(看第一張圖),但是mod卻提昇較多(幾乎是半張圖片的range),導致了這個結果
- curve fitting


C語言
- arm-angstrom-linux-gnueabi-gcc內的除法經過最佳化後,效能比減法更佳
.png)
- arm-linux-gnueabihf-gcc(linaro) (without vfpv3)
- 上下圖比較,可以看到整體在arm-angstrom-linux-gnueabi-gcc效能比較好
- 即使在這裡,單純除法比單純減法(的演算法)還快…
- 可是綜合起來看 v3更快(符合預測)

- arm-linux-gnueabihf-gcc(linaro) (with vfpv3)

效能分析: Prefetching
[ source ]
接著參考 Programming trivia: 4x4 integer matrix transpose in SSE2 實作 Intel SSE2 加速的版本,由於先進處理器架構有著 automatic/speculative prefetcher 所以這裡我們故意寫得比較 CPU-unfriendly 一些:
我們來撰寫一個使用 SSE prefetch 指令的加速版本:
接著是測試程式:
//for using x86-SSE2 intrinsics you have to include this
// PUT naive_transpose, sse_transpose, sse_prefetch_transpose HERE
// ...
int main(void)
{
//verify the result of 4x4 matrix
{
int testin[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
int testout[16];
for(int y = 0; y < 4; y++){
for(int x = 0; x < 4; x++)
printf(" %2d", testin[y*4+x]);
printf("\n");
}
printf("\n");
sse_transpose(testin, testout, 4, 4);
for(int y = 0; y < 4; y++){
for(int x = 0; x < 4; x++)
printf(" %2d", testout[y*4+x]);
printf("\n");
}
}
{
struct timeval stime, etime;
int *src =(int*)malloc(sizeof(int)*TEST_W*TEST_H);
int *out0 = (int*)malloc(sizeof(int)*TEST_W*TEST_H);
int *out1 = (int*)malloc(sizeof(int)*TEST_W*TEST_H);
int *out2 = (int*)malloc(sizeof(int)*TEST_W*TEST_H);
srand(time(NULL));
for(int y = 0; y < TEST_H; y++){
for(int x = 0; x < TEST_W; x++)
*(src + y*TEST_W + x) = rand();
}
gettimeofday(&stime, NULL);
sse_prefetch_transpose(src, out0, TEST_W, TEST_H);
printf("sse prefetch: %ld us\n", (etime.tv_sec - stime.tv_sec)*1000000 + (etime.tv_usec - stime.tv_usec));
gettimeofday(&stime, NULL);
sse_transpose(src, out1, TEST_W, TEST_H);
gettimeofday(&etime, NULL);
printf("sse: %ld us\n", (etime.tv_sec - stime.tv_sec)*1000000 + (etime.tv_usec - stime.tv_usec));
gettimeofday(&stime, NULL);
naive_transpose(src, out2, TEST_W, TEST_H);
gettimeofday(&etime, NULL);
printf("naive: %ld us\n", (etime.tv_sec - stime.tv_sec)*1000000 + (etime.tv_usec - stime.tv_usec));
free(src);
free(out0);
free(out1);
free(out2);
}
return 0;
}
存成 test.c, 並且按照下列方式編譯
$ gcc -msse2 -o test test.c
在 Intel Core i7 3612QM 執行得到下列結果:
儘管現今 CPU 有著強大的 auto-prefetcher,但是並不是所有的演算都有著規律或是線型的記憶體存取模式, 如此透過優化 prefetching 依然是能夠有相當的效能增進.
在 EeePC 900 (Intel Celeron M 900 MHz) 仍有 22% 的效能差異: