今年10-11月份參加了EDA2020(第二屆)集成電路EDA設計精英挑戰賽,通過了初賽,並參加了總決賽,最后拿了一個三等獎,雖然成績不是很好,但是想把自己做的分享一下,我所做的題目是概倫電子出的F題-快速電路仿真器(FastSPICE)中的高性能矩陣向量運算實現,下面我將給出自己的實現方案,僅供參考。
1、題目描述與分析
1.1、賽題敘述
首先先把題目寫出來:

圖1 CSR結構
1.2 、賽題分析
本賽題的描述十分簡單,要求完成一個矩陣-向量相乘的計算程序。選手們的程序的輸入是一系列大小不一的矩陣和與矩陣尺寸匹配的向量。程序需要在保證准確性的情況下,用最短的時間把矩陣與向量相乘的結果寫到內存指定的位置。
(1).API會一次性給定多個矩陣(數量<1024),每個矩陣會有一個對應的向量。選手的任務就是計算每個矩陣和向量的乘積。
(2).給定的每一個稀疏矩陣,其規模都不是特別大。最大的矩陣不會超過10000x10000。
個人思路:這道題目,需要實現double精度,且評分標准為60%看效率,30%看程序運行內存占有量,10%創新點,因此我們需要搞清楚CSR格式的結構,利用結構體指針實現double精度;而提高程序性能,可以從並行、指令集和緩存優化角度進行優化。
1)並行計算
並行計算大家都比較容易理解,所謂三個臭皮匠頂一個諸葛亮。一個大任務如果能夠切分成若干個子任務,每人完成一個,那么總的時間一般都能夠減少。並行計算的關鍵訣竅,在於【盡可能減少不同子任務之間的依賴性】。一個極端的例子,如果N個子任務之間完全不存在依賴性,各自獨立同時進行。那么這N個子任務完成的總時間與一個子任務完成的時間相同。這種情況下,我們可以認為並行的加速比為N。這個加速比是相對不並行的情況而言的。另外一個極端的例子,如果第2個子任務所需要的數據需要等第1個子任務完成才能得到,第3個子任務所需要的數據需要等第2個子任務完成才能得到,依次類推。那么這種情況,哪怕再多的人來參與這個任務,大部分的人,在大部分時間都只是在等待。這樣就無法從並行得到任何好處。一個良好的並行計算程序,需要去挖掘問題中的並行性,也就是設計並行計算方法,減少子任務之間的依賴性。那么這個題目中,你覺得怎樣的並行是最優的呢?
2)指令集優化
我們平時寫程序的時候,一般情況不會涉及到極致的性能要求。這種情況下一般不需要考慮在指令集層面進行優化。遇到像矩陣運算這一類十分底層的計算,就需要想盡一切辦法,榨干CPU的最后一點算力來提高我們程序的性能。現代的CPU中,都有SIMD的指令集,例如x86體系的SSE/AVX指令集等。所謂SIMD(Single Instruction Multiple Data)指令集指的是同一條指令,同時作用在不同的數據上。這樣可以實現指令集意義上的並行計算。需要注意的事情是,SIMD指令集並不能夠自動的讓你的程序實現加速。他需要你先將你的數據用規則的方式准備好。而稀疏矩陣的數據,本身是不規則的,也就是說非0元素有可能在任意位置。那么怎么把這些數據處理成規則的,使得SIMD指令集可以同時處理多個數據,需要各位選手自己去琢磨了。還有一點需要注意的是,將非規則的數據處理成規則的,這件事情本身也是需要額外消耗時間的。如果處理的不好,SIMD得到的收益,說不定都被這個額外消耗的時間抵消掉了。此外,除了SIMD之外,是否還有其他的指令集,可以用於稀疏矩陣運算的優化呢?這個是個開放的問題,需要大家自己調研,嘗試。
3)緩存優化
除了並行計算和指令集優化這兩個賽題中給了提示的方向。其實還有一個方向,各位同學有必要去考慮,那就是緩存優化。在計算機中,CPU與DRAM內存之間存取數據,速度是很慢的。因此現代的CPU一般都具有多級緩存。緩存的使用是CPU內部的事情,我們在程序中多數情況下不會直接操作緩存。然而,這並不意味着我們無法對緩存進行優化。如果能夠了解CPU對緩存的存取策略,我們就可以設計良好的程序,使得緩存的命中率提高,這樣就可以提高程序的性能。
1.3、實現思路
2、技術路線和實現方式
2.1、matrix_calc_taskA 算法實現
1 std::future<void> ft1 = async(std::launch::async,[&](){ 2 int count_maxtrix = 0; ////to store ---->size 3 4 5 unsigned it = 0; //to store ---->rowArraySize 6 7 int node = 0; //to store ---->rowArray[] 8 int j = 0; //to store ---->rowOffset[] 9 10 // add here to enhance the speed 11 int my_rowArraysize = 0; 12 int my_rowOffset = 0; 13 14 int maxtrix_begin = size/16; 15 int maxtrix_end = size/16*2; 16 17 18 __m256d ID_m; 19 __m256d ID_mm; 20 __m256d NormalMatrix_m; 21 __m256d columnIndice_m; 22 __m256d mux_m; 23 24 //double S_array[4]; 25 int S_count; 26 double ID_array[4]; 27 28 bool ready = 0; 29 30 for (count_maxtrix = maxtrix_begin; count_maxtrix < maxtrix_end; ++count_maxtrix) 31 { 32 33 my_rowArraysize = (*(ptr+count_maxtrix))->rowArraySize; 34 //for (it = 0; it < (*(ptr+count_maxtrix))->rowArraySize; ++it) 35 for (it = 0; it < my_rowArraysize; ++it) 36 { 37 node = (*(ptr+count_maxtrix))->rowArray[it]; 38 39 //for (j = (*(ptr+count_maxtrix))->rowOffset[node]; j < (*(ptr+count_maxtrix))->rowOffset[node + 1]; ++j) 40 my_rowOffset = (*(ptr+count_maxtrix))->rowOffset[node + 1]; 41 42 43 44 j = (*(ptr+count_maxtrix))->rowOffset[node]; 45 46 47 48 while((int)((my_rowOffset-j)/4) > 1) 49 { 50 columnIndice_m =_mm256_set_pd((*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j+3]], 51 (*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j+2]], 52 (*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j+1]], 53 (*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j]] ); 54 //ID_m = _mm256_load_pd((*(ptr+count_maxtrix))->Id+j); 55 NormalMatrix_m = _mm256_loadu_pd((*(ptr+count_maxtrix))->valueNormalMatrix+j); 56 57 58 59 mux_m = _mm256_mul_pd(NormalMatrix_m, columnIndice_m); 60 61 //ID_mm = _mm256_add_pd(ID_m, mux_m); 62 63 _mm256_storeu_pd(ID_array, mux_m); 64 65 for(S_count = 0; S_count < 4; S_count++) 66 { 67 (*(ptr+count_maxtrix))->Id[node] += ID_array[S_count]; 68 } 69 70 ready = 1; 71 72 j += 4; 73 } 74 75 if(ready == 1) 76 { 77 //for (; j < my_rowOffset; ++j) 78 while(j < my_rowOffset) 79 { 80 (*(ptr+count_maxtrix))->Id[node] += (*(ptr+count_maxtrix))->valueNormalMatrix[j] * (*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j]]; 81 ++j; 82 } 83 ready = 0; 84 } 85 else 86 { 87 //for (; j < my_rowOffset; ++j) 88 while(j < my_rowOffset) 89 { 90 (*(ptr+count_maxtrix))->Id[node] += (*(ptr+count_maxtrix))->valueNormalMatrix[j] * (*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j]]; 91 ++j; 92 } 93 } 94 95 } 96 } 97 });
圖2 TaskA實現流程圖
2.2、matrix_calc_taskB 算法實現
1 std::future<void> ft16 = async(std::launch::async,[&](){ 2 3 int count_maxtrix = 0; ////to store ---->size 4 5 int it = 0; //to store ---->rowArraySize 6 int p = 0; //to store ---->rowOffset 7 8 int row = 0; //to store ---->rowArray[x] 9 int k = 0; //to store ---->rowOffset*2 10 11 int col = 0; //to store ---->columnIndice 12 13 double cond = 0; //to store ---->valueSpiceMatrix[k] 14 double cap = 0; //to store ---->valueSpiceMatrix[k+1] 15 16 17 18 /* 19 function:Just for example ,for formula(7 8) 20 Rnx1 = Dnx1 - Gnxn*Snx1 21 Hnx1 = D'nx1 - Cnxn*Snx1 22 */ 23 int kl = 0; //to store ---->rowArray[]*2 24 double current = 0; //to store ---->D[kl] 25 double charge = 0; //to store ---->D[kl+1] 26 27 28 // add here to enhance the speed 29 int my_rowArraysize = 0; 30 int my_rowOffset = 0; 31 double my_s = 0; 32 33 int maxtrix_begin = size/16*7; 34 int maxtrix_end = size/16*8; 35 36 37 __m256d cond_m; 38 __m256d cap_m; 39 __m256d IG_Curr_m; 40 __m256d IC_Char_m; 41 __m256d A_m; 42 43 double IG_Curr_array[4]; 44 double IC_Char_array[4]; 45 double A_m_array[4]; 46 int u_count; 47 bool ready = 0; 48 49 for (count_maxtrix = maxtrix_begin; count_maxtrix < maxtrix_end; ++count_maxtrix) 50 { 51 52 //for (it = 0; it < (*(ptr+count_maxtrix))->rowArraySize; ++it) 53 my_rowArraysize = (*(ptr+count_maxtrix))->rowArraySize; 54 for (it = 0; it < my_rowArraysize; ++it) 55 { 56 //…………………………………………formula(4 5)、(7 8)、(9) Share a for Loop……………………………………………………………… 57 /* 58 Function:Just for example ,for formula(4 5) 59 IGnx1 = Gnxn*Snx1 60 ICnx1 = Cnxn*Snx1 61 */ 62 row = (*(ptr+count_maxtrix))->rowArray[it]; //share 63 64 65 66 67 /* 68 function:Just for example ,for formula(7 8) 69 Rnx1 = Dnx1 - Gnxn*Snx1 70 Hnx1 = D'nx1 - Cnxn*Snx1 71 */ 72 kl = row * 2; 73 74 current = (*(ptr+count_maxtrix))->D[kl]; 75 charge = (*(ptr+count_maxtrix))->D[kl + 1]; 76 77 78 79 //for (p = (*(ptr+count_maxtrix))->rowOffset[row]; p < (*(ptr+count_maxtrix))->rowOffset[row + 1]; ++p) 80 my_rowOffset = (*(ptr+count_maxtrix))->rowOffset[row + 1]; 81 82 83 p = (*(ptr+count_maxtrix))->rowOffset[row]; 84 //for (p = (*(ptr+count_maxtrix))->rowOffset[row]; p < my_rowOffset; ++p) 85 while((int)((my_rowOffset-p)/4) > 1) 86 { 87 //col = (*(ptr+count_maxtrix))->columnIndice[p]; 88 k = p * 2; 89 //cond = (*(ptr+count_maxtrix))->valueSpiceMatrix[k]; 90 //cap = (*(ptr+count_maxtrix))->valueSpiceMatrix[k + 1]; 91 92 93 94 /* 95 Function:Just for example ,for formula(4 5) 96 IGnx1 = Gnxn*Snx1 97 ICnx1 = Cnxn*Snx1 98 */ 99 100 //my_s = (*(ptr+count_maxtrix))->S[col]; 101 102 //_mm256_blend_pd (__m256d a, __m256d b, const int imm8); 103 104 cond_m = _mm256_set_pd((*(ptr+count_maxtrix))->valueSpiceMatrix[k+6], 105 (*(ptr+count_maxtrix))->valueSpiceMatrix[k+4], 106 (*(ptr+count_maxtrix))->valueSpiceMatrix[k+2], 107 (*(ptr+count_maxtrix))->valueSpiceMatrix[k]); 108 109 110 cap_m = _mm256_set_pd((*(ptr+count_maxtrix))->valueSpiceMatrix[k + 7], 111 (*(ptr+count_maxtrix))->valueSpiceMatrix[k + 5], 112 (*(ptr+count_maxtrix))->valueSpiceMatrix[k + 3], 113 (*(ptr+count_maxtrix))->valueSpiceMatrix[k + 1]); 114 /* 115 alpha_m = _mm256_set_pd((*(ptr+count_maxtrix))->alpha, 116 (*(ptr+count_maxtrix))->alpha, 117 (*(ptr+count_maxtrix))->alpha, 118 (*(ptr+count_maxtrix))->alpha); 119 120 my_s_m = _mm256_set_pd((*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p+3]] 121 ,(*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p+2]] 122 ,(*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p+1]] 123 ,(*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p]]); */ 124 125 IG_Curr_m = _mm256_mul_pd(cond_m 126 ,_mm256_set_pd((*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p+3]] 127 ,(*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p+2]] 128 ,(*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p+1]] 129 ,(*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p]])); 130 IC_Char_m = _mm256_mul_pd(cap_m 131 ,_mm256_set_pd((*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p+3]] 132 ,(*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p+2]] 133 ,(*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p+1]] 134 ,(*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[p]])); 135 136 A_m = _mm256_add_pd(cond_m, _mm256_mul_pd(_mm256_set_pd((*(ptr+count_maxtrix))->alpha, 137 (*(ptr+count_maxtrix))->alpha, 138 (*(ptr+count_maxtrix))->alpha, 139 (*(ptr+count_maxtrix))->alpha) 140 ,cap_m)); 141 142 143 _mm256_storeu_pd(IG_Curr_array, IG_Curr_m); 144 _mm256_storeu_pd(IC_Char_array, IC_Char_m); 145 _mm256_storeu_pd(A_m_array, A_m); 146 147 148 for(u_count = 0; u_count < 4; u_count++) 149 { 150 //(*(ptr+count_maxtrix))->Id[node] += ID_array[S_count]; 151 152 (*(ptr+count_maxtrix))->IG[row] += IG_Curr_array[u_count]; 153 (*(ptr+count_maxtrix))->IC[row] += IC_Char_array[u_count]; 154 155 /* 156 current -= (*(ptr+count_maxtrix))->valueSpiceMatrix[k] * (*(ptr+count_maxtrix))->S[col]; 157 charge -= (*(ptr+count_maxtrix))->valueSpiceMatrix[k + 1] * (*(ptr+count_maxtrix))->S[col]; 158 */ 159 current -= IG_Curr_array[u_count]; 160 charge -= IC_Char_array[u_count]; 161 162 163 /* 164 function:Just for example ,for formula(9) 165 Anxn = Gnxn + alpha*Cnxn 166 */ 167 (*(ptr+count_maxtrix))->A[p+u_count] = A_m_array[u_count]; 168 } 169 170 171 ready = 1; 172 p += 4; 173 174 } 175 176 if(ready == 1) 177 { 178 //for (; j < my_rowOffset; ++j) 179 while(p < my_rowOffset) 180 { 181 col = (*(ptr+count_maxtrix))->columnIndice[p]; 182 k = p * 2; 183 cond = (*(ptr+count_maxtrix))->valueSpiceMatrix[k]; 184 cap = (*(ptr+count_maxtrix))->valueSpiceMatrix[k + 1]; 185 186 187 188 /* 189 Function:Just for example ,for formula(4 5) 190 IGnx1 = Gnxn*Snx1 191 ICnx1 = Cnxn*Snx1 192 */ 193 194 my_s = (*(ptr+count_maxtrix))->S[col]; 195 (*(ptr+count_maxtrix))->IG[row] += cond * my_s; 196 (*(ptr+count_maxtrix))->IC[row] += cap * my_s; 197 198 199 200 201 /* current -= (*(ptr+count_maxtrix))->valueSpiceMatrix[k] * (*(ptr+count_maxtrix))->S[col]; 202 charge -= (*(ptr+count_maxtrix))->valueSpiceMatrix[k + 1] * (*(ptr+count_maxtrix))->S[col]; 203 */ 204 current -= cond * my_s; 205 charge -= cap * my_s; 206 207 208 /* 209 function:Just for example ,for formula(9) 210 Anxn = Gnxn + alpha*Cnxn 211 */ 212 (*(ptr+count_maxtrix))->A[p] = cond + (*(ptr+count_maxtrix))->alpha * cap; 213 ++p; 214 } 215 ready = 0; 216 } 217 else 218 { 219 //for (; j < my_rowOffset; ++j) 220 while(p < my_rowOffset) 221 { 222 col = (*(ptr+count_maxtrix))->columnIndice[p]; 223 k = p * 2; 224 cond = (*(ptr+count_maxtrix))->valueSpiceMatrix[k]; 225 cap = (*(ptr+count_maxtrix))->valueSpiceMatrix[k + 1]; 226 227 228 229 /* 230 Function:Just for example ,for formula(4 5) 231 IGnx1 = Gnxn*Snx1 232 ICnx1 = Cnxn*Snx1 233 */ 234 235 my_s = (*(ptr+count_maxtrix))->S[col]; 236 (*(ptr+count_maxtrix))->IG[row] += cond * my_s; 237 (*(ptr+count_maxtrix))->IC[row] += cap * my_s; 238 239 240 241 242 /* current -= (*(ptr+count_maxtrix))->valueSpiceMatrix[k] * (*(ptr+count_maxtrix))->S[col]; 243 charge -= (*(ptr+count_maxtrix))->valueSpiceMatrix[k + 1] * (*(ptr+count_maxtrix))->S[col]; 244 */ 245 current -= cond * my_s; 246 charge -= cap * my_s; 247 248 249 /* 250 function:Just for example ,for formula(9) 251 Anxn = Gnxn + alpha*Cnxn 252 */ 253 (*(ptr+count_maxtrix))->A[p] = cond + (*(ptr+count_maxtrix))->alpha * cap; 254 255 ++p; 256 } 257 } 258 259 260 /* 261 function:Just for example ,for formula(7 8) 262 Rnx1 = Dnx1 - Gnxn*Snx1 263 Hnx1 = D'nx1 - Cnxn*Snx1 264 */ 265 (*(ptr+count_maxtrix))->R[row] = current; 266 (*(ptr+count_maxtrix))->H[row] = charge; 267 268 269 270 271 } 272 } 273 });
圖3 TaskB程序實現流程圖
2.3、線程加速仿真網表計算實現


圖 5線程加速示意圖
2.4、AVX 指令集仿真網表計算實現

1 columnIndice_m =_mm256_set_pd((*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j+3]], 2 (*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j+2]], 3 (*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j+1]], 4 (*(ptr+count_maxtrix))->S[(*(ptr+count_maxtrix))->columnIndice[j]] ); 5 //ID_m = _mm256_load_pd((*(ptr+count_maxtrix))->Id+j); 6 NormalMatrix_m = _mm256_loadu_pd((*(ptr+count_maxtrix))->valueNormalMatrix+j); 7 8 9 10 mux_m = _mm256_mul_pd(NormalMatrix_m, columnIndice_m); 11 12 //ID_mm = _mm256_add_pd(ID_m, mux_m); 13 14 _mm256_storeu_pd(ID_array, mux_m);
3、TaskA 與 TaskB 任務 testApp 版本數據測試
3.1、matrix_calc_taskA 任務的實現與測試數據


3.2、TaskA 與 TaskB 任務 nanospice 電路仿真器和網表數據測試

4、設計總結及擴展