程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 用SSE指令集增強浮點運算性能

用SSE指令集增強浮點運算性能

編輯:關於C語言

SSE是很常見的一個X86平台的指令集,早在P4時代就已經出現了。後來INTEL又接連著推出了SSE2,SSE3,SSE4等(不過可沒有SSE5,原本規劃是有的,後來INTEL獨立發展了新一代AVX指令集旨在取代SSE,關於AVX現在資料還不是很多,用的也沒有SSE普遍。畢竟支持AVX的CPU也不多,像我的T4400就不支持)。
廢話不多說,還是來點實在的東西。大家都知道浮點數運算比起整數運算,速度的確是非常緩慢,很多領域比如圖像處理中,需要大量用到浮點數運算,此時CPU就是一個很顯著的瓶頸,為了提高浮點數性能,我們有兩個方法:
1,化浮點為整形:即盡量通過某種數學變換將原來的浮點數運算變成整數運算。
2,使用SSE這類指令集:顯然這種方法是本文重點,不過方法1也會一並用起。
以一個很常見的圖像彩色轉灰度為例。
根據色彩學上的一些理論,將一個RGB彩色像素轉換成灰度,實際上是一個1*3矩陣和一個3*1矩陣相乘,說白了就是如下過程:
設原像素為p0 = (r0,g0,b0),轉換為s=(r0*0.3,g0*0.6,b0*0.1),然後新的灰度像素p1=(s,s,s)。
這裡可以看到,求得s值這一步中,有三次浮點運算,我們可以用方法1將這裡的浮點運算暫時化為整數(全部乘以10),即
s=(r0*3,g0*6,b0*1),最後一次性除以10。
具體代碼如下:
void doProcess(PBYTE pIn, DWORD size, DWORD width, DWORD height, DWORD bitCount)
{
  DWORD dwRGBSum = 0;
  for(DWORD dwIndex = 0; dwIndex < size; dwIndex+=3)
  {
    dwRGBSum =   
      1 * pIn[dwIndex+0] +     //Blue 
      6 * pIn[dwIndex+1] +     //Green 
      3 * pIn[dwIndex+2];        //Red 
    dwRGBSum /= 10.0;
    pIn[dwIndex+0] = dwRGBSum;
    pIn[dwIndex+1] = dwRGBSum;
    pIn[dwIndex+2] = dwRGBSum;
  }
}
現在我們再來使用SSE來進一步優化。
SSE一次性可以處理128位的運算,即4個浮點數。因而我們將四次除法放在一次進行,核心的一個數據結構是__m128,這是一個聯合體,具體參見其源碼。

SSE中批量浮點數乘法對應的C函數是_mm_mul_ps。用法可以參考MSDN或者INTEL官方網站上的一個PDF。

void doProcess(PBYTE pIn, DWORD size, DWORD width, DWORD height, DWORD bitCount)
{
  UINT16 dwRGBSum0 = 0;
  UINT16 dwRGBSum1 = 0;
  UINT16 dwRGBSum2 = 0;
  UINT16 dwRGBSum3 = 0;

  for(DWORD idx = 0; idx < size; idx+=12)
  {
    dwRGBSum0 =   
      1 * pIn[idx+0] +     //Blue
      6 * pIn[idx+1] +     //Green
      3 * pIn[idx+2];        //Red

    dwRGBSum1 =   
      1 * pIn[idx+3] +     //Blue
      6 * pIn[idx+4] +     //Green
      3 * pIn[idx+5];        //Red

    dwRGBSum2 =   
      1 * pIn[idx+6] +     //Blue
      6 * pIn[idx+7] +     //Green
      3 * pIn[idx+8];        //Red

    dwRGBSum3 =   
      1 * pIn[idx+9] +     //Blue
      6 * pIn[idx+10] +     //Green
      3 * pIn[idx+11];        //Red


    __m128 old = _mm_set_ps(dwRGBSum0, dwRGBSum1, dwRGBSum2, dwRGBSum3);
    __m128 ret = _mm_mul_ps(old, vec);

    pIn[idx+0] = pIn[idx+1] = pIn[idx+2] = (BYTE)ret.m128_f32[3];
    pIn[idx+3] = pIn[idx+4] = pIn[idx+5] = (BYTE)ret.m128_f32[2];
    pIn[idx+6] = pIn[idx+7] = pIn[idx+8] = (BYTE)ret.m128_f32[1];
    pIn[idx+9] = pIn[idx+10] = pIn[idx+11] = (BYTE)ret.m128_f32[0];
  }
}
代碼看上去要比原來的復雜許多,不過其實原理很簡單的,原來一次性處理一個像素,現在一次性處理4個,性能和效率大大提升了。

這個代碼其實還可以優化的,因為SSE內除了浮點可以批量處理,整數也是可以的,對應的數據結構是__m128i。涉及到一些矩陣的知識,我就不多說了。

上面這個優化性能測試結果還是很明顯的,原來的程序對一個2560*1600,24位色深的圖片進行轉換,需要將近800ms的時間,優化後,只需450ms了,提高了將近一倍。

本文出自 “Kevx's Blog” 博客

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved