題目描述
這有一份由 pthread 撰寫的序列總和計算,假設不開任何的優化參數,在快取處理會有嚴重缺失。
main.c
輸入序列長度 $n$,計算 $m$ 次經由 $\text{key}, \; \text{key} + 1, \; \text{key} + 2, \; \cdots, \; \text{key} + m-1$ 加密的序列總和。這部份將不提供修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <stdio.h> #include <stdlib.h> #include "utils.h" int main() { int cases = 0; int n, m, key; scanf("%d %d %d", &n, &m, &key); for (int it = 0; it < m; it++) { int ret = run(n, key); printf("Case #%d: %d\n", ++cases, ret); key++; } return 0; }
|
utils.h
計算工作交由 void f(int n, int key, int *p1, int *p2, int *p3, int *p4);
完成最後四個參數,將會由 4 個 thread 計算分別儲存在位址 p1
, p2
, p3
, p4
中。
1 2 3 4 5 6 7
| #ifndef __UTILS_H #define __UTILS_H void f(int n, int key, int *p1, int *p2, int *p3, int *p4); int run(int n, int key); #endif
|
sum.c
平行計算序列總和,特別注意到 void* subtask(void* argu)
中存取的記憶體位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include <stdlib.h> #include <pthread.h> #include <stdint.h> #include "utils.h" typedef struct Argu { int *result; int L, R, key; } Argu; static inline uint32_t rotate_left(uint32_t x, uint32_t n) { return (x << n) | (x >> (32-n)); } static inline uint32_t encrypt(uint32_t m, uint32_t key) { return (rotate_left(m, key&31) + key)^key; } static void* subtask(void* argu) { Argu *ptr = (Argu *) argu; *(ptr->result) = 0; for (int i = ptr->L; i <= ptr->R; i++) *(ptr->result) += encrypt(i, ptr->key); } void f(int n, int key, int *p1, int *p2, int *p3, int *p4) { pthread_t threads[4]; int *output[4] = {p1, p2, p3, p4}; for (int i = 0; i < 4; i++) { Argu *argu = (Argu *) malloc(sizeof(Argu)); argu->result = output[i]; argu->L = i * (n / 4) + 1; argu->R = argu->L + n / 4 - 1; argu->key = key; if (i == 3) argu->R = n; pthread_create(&threads[i], NULL, subtask, argu); } for (int i = 0; i < 4; i++) pthread_join(threads[i], NULL); }
|
job.c
你的工作要改善下方代碼的效能。
1 2 3 4 5 6 7 8 9 10
| #include "utils.h" int ret[128]; int run(int n, int key) { int sum = 0; f(n, key, ret, ret+1, ret+2, ret+3); for (int i = 0; i < 4; i++) sum += ret[i]; return sum; }
|
輸入格式
輸入只有一行三個整數 $n, \; m, \; \text{key}$。
輸出格式
輸出 $m$ 序列總和結果 (無視 overflow)。
範例輸入
範例輸出
1 2 3 4 5 6 7 8 9 10
| Case #1: 1397862656 Case #2: 1970821632 Case #3: -1178356736 Case #4: 1113221120 Case #5: 1401409536 Case #6: 1977786368 Case #7: -1164427264 Case #8: 1145914243 Case #9: 645957382 Case #10: 1308383748
|
編譯環境
Makefile
1 2 3 4 5 6
| CFLAG=-std=c99 -pthread all: main.c sum.c job.c gcc $(CFLAG) main.c -c gcc $(CFLAG) sum.c -c gcc $(CFLAG) main.o sum.o job.c -o job
|
reference
Solution
由於茵可大神給予我參考的簡報 Mind Cache 中提到不少的快取實驗,其中一部份就是在 thread 使用上,於是就拿來出題測試測試一番。
通常講到數據局部性 (Data Locality) 都希望資料盡量集中,但一不小心會犯下錯誤,就是在多個 thread 儲存答案時,雖然不會共用同一個位址,但相鄰的位址在另一個核 (core) 使用,由於彼此之間相互修改,兩個相鄰位址若放在同一個 cache line,dirty bit 勢必要讓他們從 cache line 掉到 L1 -> L2 -> L3,進行資料同步。
目前 cache line 普遍上設計大小為 64 Bytes,相當於間隔 16 個 4 bytes Integer 的差距,所以儲存答案間隔遠一點會比近一點好。當然,編譯器優化參數開到 O2 以上時,似乎就直接先存放到暫存器裡頭,不會不斷地存取 global memory 部份,如此一來,就不會發生 cache miss 問題。實驗結果效能可以差到四倍左右。
1 2 3 4 5 6 7 8 9
| #include "utils.h" int ret[128]; int run(int n, int key) { int sum = 0; f(n, key, ret+0, ret+16, ret+32, ret+48); sum = ret[0] + ret[16] + ret[32] + ret[48]; return sum; }
|