SSE用いたアルファブレンドの高速化2
Posted on Sat 20 March 2010 in ペイントツール
ロード・ストアに使用するSSE命令
ロード
イントリンシック命令 | 対応するSSE命令 | |
---|---|---|
_mm_loadu_ps | MOVUPS | アライメントを合わせる必要がない |
_mm_load_ps | MOVAPS | アライメントを合わせなければならない |
ストア
イントリンシック命令 | 対応するSSE命令 | |
---|---|---|
_mm_storeu_ps | MOVUPS | アライメントを合わせる必要がない |
_mm_store_ps | MOVAPS | アライメントを合わせなければならない |
_mm_stream_si128 | MOVNTPD | アライメントを合わせなければならない、キャッシュを介さずにストアする |
計測するアルファブレンド処理
32bit,64bit向けに2通りに実行ファイルを作成、それぞれについて
- SSEを使わない処理
- _mm_loadu_psでメモリ読み込み、_mm_storeu_psでメモリ書き込み
- _mm_load_psでメモリ読み込み、_mm_store_psでメモリ書き込み
- _mm_load_psでメモリ読み込み、_mm_stream_si128でメモリ書き込み
の4通りでテストする。
64bit向けの設定だと__asmのとこでエラーがでるのでインラインアセンブラではなく組み込み関数を使って書きました。
アライメントをあわせて処理するために、128bit(4pixel)ずつ読み込んで処理を行っています。
計測方法
- 5760x3600BGR画像を2枚読み込み、アルファ値を指定してアルファブレンドを行う。
- 画像の保存、読み込みはopenCV 2.3を用いた。
- アルファブレンド部分の処理のみ処理時間を計測した。
計測に使用した環境
- CPU: Core i7 2600 3.40GHz
- OS: windows 7 64bit
- コンパイラ: Visual Studio 2010
- 最適化オプション: 実行速度 (/O2)
コード
#include <iostream>
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include "QPCTimer.h"
typedef unsigned char uint8_t;
typedef unsigned char int8_t;
typedef unsigned short uint16_t;
typedef short int16_t;
typedef unsigned long uint32_t;
typedef long int32_t;
union UCvPixel32
{
uint32_t value;
struct
{
uint8_t b,g,r,a;
};
};
typedef UCvPixel32 UCvPixel;
/////////////////////////////////////////////
/*!
指定位置のRGB値を取得する
@param[in] image 画像
@param[in] x X座標
@param[in] y Y座標
@param[out] dst 取得した値
*/
inline void GetPixelFromBGR(const IplImage *image, int x, int y, UCvPixel *dst)
{
assert(image);
assert(image->nChannels == 3);
assert((0 <= x && x <= image->width-1) && (0 <= y && y <= image->height-1));
memcpy(&(dst->value), image->imageData + y*image->widthStep + x*image->nChannels, sizeof(uint8_t)*3);
// *r = (uint8_t) image->imageData[y *image->widthStep+ x * image->nChannels + 2];
// *g = (uint8_t) image->imageData[y *image->widthStep+ x * image->nChannels + 1];
// *b = (uint8_t) image->imageData[y *image->widthStep+ x * image->nChannels];
}
//////////////////////////////////////////////
/*!
指定位置にRGB値をセットする
@param[in] image 画像
@param[in] x X座標
@param[in] y Y座標
@param[in] src セットする値
*/
inline void SetPixelToBGR(
IplImage *image,
int x,
int y,
const UCvPixel* src)
{
assert(image);
assert(image->nChannels == 3);
assert((0 <= x && x <= image->width-1) && (0 <= y && y <= image->height-1));
memcpy(image->imageData + y*image->widthStep + x*image->nChannels, &(src->value), sizeof(uint8_t)*3);
//image->imageData[y *image->widthStep+ x * image->nChannels + 2] = r;
//image->imageData[y *image->widthStep+ x * image->nChannels + 1] = g;
//image->imageData[y *image->widthStep+ x * image->nChannels] = b;
}
int main(void)
{
int x,y;
int alpha = 150; //アルファ値150でアルファブレンドを行う
int r,g,b;
int r1, g1, b1;
int r2, g2, b2;
int ra = 255 - alpha;
UCvPixel *buf1;
UCvPixel *buf2;
UCvPixel *buf3;
IplImage* test1 = cvLoadImage("test1.bmp");
IplImage* test2 = cvLoadImage("test2.bmp");
IplImage* b_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
IplImage* g_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
IplImage* r_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
IplImage* a_ch = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 1);
IplImage* img1 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
IplImage* img2 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
IplImage* img3 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
IplImage* img4 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
IplImage* img5 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
IplImage* img6 = cvCreateImage(cvGetSize(test1), IPL_DEPTH_8U, 4);
cvSet(a_ch, cvScalar(255));
cvSplit(test1, b_ch, g_ch, r_ch, NULL);
cvMerge(b_ch, g_ch, r_ch, a_ch, img1);
cvSplit(test2, b_ch, g_ch, r_ch, NULL);
cvMerge(b_ch, g_ch, r_ch, a_ch, img2);
//非SSE
IETimer timer;
for(y=0; y<img1->height; y++){
for(x=0; x<img1->width; x++){
buf1 = GetPixelAddress32(img1, x, y);
buf2 = GetPixelAddress32(img2, x, y);
buf3 = GetPixelAddress32(img3, x, y);
buf3->b = (buf1->b*ra + buf2->b * alpha) >> 8;
buf3->g = (buf1->g*ra + buf2->g * alpha) >> 8;
buf3->r = (buf1->r*ra + buf2->r * alpha) >> 8;
}
}
std::cout << timer.elapsed() << " msec" << std::endl;
cvSaveImage("test3.bmp", img3);
//SSE: _mm_loadu_ps + _mm_storeu_ps
timer.restart();
for(y=0; y<img1->height; y++){
for(x=0; x<img1->width; x+=4){
buf1 = GetPixelAddress32(img1, x, y);
buf2 = GetPixelAddress32(img2, x, y);
buf3 = GetPixelAddress32(img4, x, y);
__m128i under = _mm_castps_si128( _mm_loadu_ps((float*)buf1) );
__m128i over = _mm_castps_si128( _mm_loadu_ps((float*)buf2) );
__m128i xmalpha = _mm_set1_epi16(alpha);
__m128i xmra = _mm_set1_epi16(255-alpha);
__m128i xmzero = _mm_setzero_si128();
//calc lo quad word
__m128i xmo = _mm_unpacklo_epi8(over, xmzero);
__m128i xmu = _mm_unpacklo_epi8(under, xmzero);
xmo = _mm_mullo_epi16(xmo, xmalpha); //(over*(255-alpha)
xmu = _mm_mullo_epi16(xmu, xmra); //under * alpha
xmo = _mm_add_epi16(xmo, xmu); //(over*(255 - alpha) + under*alpha)
__m128i xmlo = _mm_srli_epi16(xmo, 8); //(over*(255 - alpha) + under*alpha) >> 8
//calc hi quad word
xmo = _mm_unpackhi_epi8(over, xmzero);
xmu = _mm_unpackhi_epi8(under, xmzero);
xmo = _mm_mullo_epi16(xmo, xmalpha); //(over*(255-alpha)
xmu = _mm_mullo_epi16(xmu, xmra); //under * alpha
xmo = _mm_add_epi16(xmo, xmu); //(over*(255 - alpha) + under*alpha)
__m128i xmhi = _mm_srli_epi16(xmo, 8); //(over*(255 - alpha) + under*alpha) >> 8
//store to memory
_mm_storeu_ps((float*)buf3,
_mm_castsi128_ps( _mm_packus_epi16(xmlo, xmhi) ));
}
}
std::cout << timer.elapsed() << " msec" << std::endl;
cvSaveImage("test4.bmp", img4);
//SSE: _mm_load_ps + _mm_store_ps
timer.restart();
for(y=0; y<img1->height; y++){
for(x=0; x<img1->width; x+=4){
buf1 = GetPixelAddress32(img1, x, y);
buf2 = GetPixelAddress32(img2, x, y);
buf3 = GetPixelAddress32(img5, x, y);
__m128i under = _mm_castps_si128( _mm_load_ps((float*)buf1) );
__m128i over = _mm_castps_si128( _mm_load_ps((float*)buf2) );
__m128i xmalpha = _mm_set1_epi16(alpha);
__m128i xmra = _mm_set1_epi16(255-alpha);
__m128i xmzero = _mm_setzero_si128();
//calc lo quad word
__m128i xmo = _mm_unpacklo_epi8(over, xmzero);
__m128i xmu = _mm_unpacklo_epi8(under, xmzero);
xmo = _mm_mullo_epi16(xmo, xmalpha); //(over*(255-alpha)
xmu = _mm_mullo_epi16(xmu, xmra); //under * alpha
xmo = _mm_add_epi16(xmo, xmu); //(over*(255 - alpha) + under*alpha)
__m128i xmlo = _mm_srli_epi16(xmo, 8); //(over*(255 - alpha) + under*alpha) >> 8
//calc hi quad word
xmo = _mm_unpackhi_epi8(over, xmzero);
xmu = _mm_unpackhi_epi8(under, xmzero);
xmo = _mm_mullo_epi16(xmo, xmalpha); //(over*(255-alpha)
xmu = _mm_mullo_epi16(xmu, xmra); //under * alpha
xmo = _mm_add_epi16(xmo, xmu); //(over*(255 - alpha) + under*alpha)
__m128i xmhi = _mm_srli_epi16(xmo, 8); //(over*(255 - alpha) + under*alpha) >> 8
//store to memory
_mm_store_ps((float*)buf3,
_mm_castsi128_ps( _mm_packus_epi16(xmlo, xmhi) ));
}
}
std::cout << timer.elapsed() << " msec" << std::endl;
cvSaveImage("test5.bmp", img5);
//SSE: _mm_load_ps + _mm_stream_si128
timer.restart();
for(y=0; y<img1->height; y++){
for(x=0; x<img1->width; x+=4){
buf1 = GetPixelAddress32(img1, x, y);
buf2 = GetPixelAddress32(img2, x, y);
buf3 = GetPixelAddress32(img6, x, y);
__m128i under = _mm_castps_si128( _mm_load_ps((float*)buf1) );
__m128i over = _mm_castps_si128( _mm_load_ps((float*)buf2) );
__m128i xmalpha = _mm_set1_epi16(alpha);
__m128i xmra = _mm_set1_epi16(255-alpha);
__m128i xmzero = _mm_setzero_si128();
//calc lo quad word
__m128i xmo = _mm_unpacklo_epi8(over, xmzero);
__m128i xmu = _mm_unpacklo_epi8(under, xmzero);
xmo = _mm_mullo_epi16(xmo, xmalpha); //(over*(255-alpha)
xmu = _mm_mullo_epi16(xmu, xmra); //under * alpha
xmo = _mm_add_epi16(xmo, xmu); //(over*(255 - alpha) + under*alpha)
__m128i xmlo = _mm_srli_epi16(xmo, 8); //(over*(255 - alpha) + under*alpha) >> 8
//calc hi quad word
xmo = _mm_unpackhi_epi8(over, xmzero);
xmu = _mm_unpackhi_epi8(under, xmzero);
xmo = _mm_mullo_epi16(xmo, xmalpha); //(over*(255-alpha)
xmu = _mm_mullo_epi16(xmu, xmra); //under * alpha
xmo = _mm_add_epi16(xmo, xmu); //(over*(255 - alpha) + under*alpha)
__m128i xmhi = _mm_srli_epi16(xmo, 8); //(over*(255 - alpha) + under*alpha) >> 8
//store to memory
_mm_stream_si128((__m128i*)buf3, _mm_packus_epi16(xmlo, xmhi));
}
}
std::cout << timer.elapsed() << " msec" << std::endl;
cvSaveImage("test6.bmp", img6);
cvReleaseImage(&b_ch);
cvReleaseImage(&g_ch);
cvReleaseImage(&r_ch);
cvReleaseImage(&a_ch);
cvReleaseImage(&img1);
cvReleaseImage(&img2);
cvReleaseImage(&img3);
cvReleaseImage(&img4);
cvReleaseImage(&img5);
cvReleaseImage(&img6);
}
計測結果
32bit
非SSE | _mm_loadu_ps + _mm_storeu_ps | _mm_load_ps + _mm_store_ps | _mm_load_ps + _mm_stream_si128 | |
---|---|---|---|---|
1回目 | 158.012 msec | 58.221 msec | 55.867 msec | 57.354 msec |
2回目 | 148.493 msec | 61.607 msec | 48.364 msec | 43.348 msec |
3回目 | 213.018 msec | 48.586 msec | 51.439 msec | 45.258 msec |
64bit
非SSE | _mm_loadu_ps + _mm_storeu_ps | _mm_load_ps + _mm_store_ps | _mm_load_ps + _mm_stream_si128 | |
---|---|---|---|---|
1回目 | 155.736 msec | 55.290 msec | 65.135 msec | 58.095 msec |
2回目 | 153.897 msec | 52.778 msec | 57.789 msec | 60.994 msec |
3回目 | 165.184 msec | 54.621 msec | 51.432 msec | 47.805 msec |
まとめ
32bit、64bitともにSSEにより3倍程度高速化できた。
[_mm_loadu_ps + _mm_storeu_ps], [_mm_load_ps +
_mm_store_ps], [_mm_load_ps +
_mm_stream_si128]の3通りでSSE命令によるロード・ストアを実装したが、今回の実行環境ではバラツキがあり、特に優れている命令はわからなかった。
アライメントがずれている場合などでは、違いが出てくるかもしれない。