去年年底把工程項目由VS的2015升級到2019版本,本以爲直接配置下運行環境就可以了,但是一編譯發現一大堆錯誤,所有的錯誤都指向一系列的指令集,比如_mm_exp_ps、_mm_log_ps、_mm_pow_ps等等,後面發現原來從2019版本開始,編譯器已經自帶了這些常用的函數,所以自己函數和系統的重名了,也就無法通過編譯了。
這個時候只能把自己大函數名都適當的進行修改,再重新編譯了.
我們在intel的關於指令集方面的官方網站也發現了一些信息: 比如_mm_exp_ps,其說明如下:
注意其中的Sequence說明這是由一些其他的指令組合而成的。
既然系統也提供了這類函數,那麼他們的效率和精度和我們自己寫的相比又有多大的差異和不同呢,一直想做個比較,今天就抽點時間做點測試.
我們先看看精度,以exp爲代表,測試代碼如下:
__m128 s = _mm_setr_ps(0, 3, 4, 6); __m128 d1 = _mm_exp_ps(s); __m128 d2 = _mm_fexp_ps(s); for (int i = 0; i < 4; i++) { printf("%f %f %f \n", d1.m128_f32[i], d2.m128_f32[i], exp(s.m128_f32[i])); }
運行結果如下所示:
其中的_mm_fexp_ps的代碼來自於github上的sse_mathfun文件裏。
可見大家的精度上差不多,在某些情況下和標準的數學結果都有差異。 同樣測試了sin,cos,log,pow等函數,精度也都差不多,說明大家的計算方法差異不大。
下面再測試下速度,測試代碼如下所示:
LARGE_INTEGER nEndTime, nBeginTime, nFreq; double time; QueryPerformanceFrequency(&nFreq); int Length = 100000; float* Src = (float*)malloc(Length * sizeof(float)); float* Dest = (float*)malloc(Length * sizeof(float)); QueryPerformanceCounter(&nBeginTime);//獲取開始時刻計數值 for (int Y = 0; Y < 10000; Y++) { for (int X = 0; X < Length; X += 4) { __m128 SrcV = _mm_loadu_ps(Src + X); __m128 DstV = _mm_exp_ps(SrcV); _mm_storeu_ps(Dest + X, DstV); } } QueryPerformanceCounter(&nEndTime);//獲取停止時刻計數值 time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) * 1000 / (double)nFreq.QuadPart;//(開始-停止)/頻率即爲秒數,精確到小數點後6位 printf("%f \n", time); QueryPerformanceCounter(&nBeginTime);//獲取開始時刻計數值 for (int Y = 0; Y < 10000; Y++) { for (int X = 0; X < Length; X += 4) { __m128 SrcV = _mm_loadu_ps(Src + X); __m128 DstV = _mm_fexp_ps(SrcV); _mm_storeu_ps(Dest + X, DstV); } } QueryPerformanceCounter(&nEndTime);//獲取停止時刻計數值 time = (double)(nEndTime.QuadPart - nBeginTime.QuadPart) * 1000 / (double)nFreq.QuadPart;//(開始-停止)/頻率即爲秒數,精確到小數點後6位 printf("%f \n", time); free(Src); free(Dest);
不同的函數,測試耗時比較如下表:
綜合比較起來,似乎自定義的函數含有較大的優勢,所以可以自行取捨了。
另外,還注意到,在標準的SSE指令集裏,沒有針對整數的除法指令,而在VS2019自帶的指令裏,已經有了這些函數,當然他們也不是原生的指令,而是多個指令組合的。我們測試了其中的一個函數_mm_div_epi32,發現這個的速度並不理想,比自己寫的要差一個檔次,而且他對除零還是直接報錯誤,所以這個方面的東西還是自己弄比較好,比如我們自定的四個32位整數除法如下代碼:
// 四個浮點數的除法a/b,如果b中某個分量爲0,則對應位置返回0值 inline __m128 _mm_divz_ps(__m128 a, __m128 b) { //return _mm_blendv_ps(_mm_div_ps(a, b), _mm_setzero_ps(), _mm_cmpeq_ps(b, _mm_setzero_ps())); return _mm_and_ps(_mm_div_ps(a, b), _mm_cmpneq_ps(b, _mm_setzero_ps())); } // 四個32位整數的除法,當某個除數爲0時,返回0 inline __m128i _mm_divz_epi32(__m128i A, __m128i B) { return _mm_cvtps_epi32(_mm_divz_ps(_mm_cvtepi32_ps(A), _mm_cvtepi32_ps(B))); }
即使在除數確當不爲0的情況下,系統自帶的_mm_div_epi32函數也要比_mm_divz_epi32慢2倍以上,所以目前也不清楚這個是爲什麼。 當然,VS2019及其以上版本確實提供了很多原來沒有指令集函數,如果想快速的實現某些功能,這些確實是一大利器。但是知道他們各自的特性對於做特定條件下的優化還是很有意義的。
翻譯
搜索
複製