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を用いた。
  • アルファブレンド部分の処理のみ処理時間を計測した。

計測に使用した環境

コード

#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命令によるロード・ストアを実装したが、今回の実行環境ではバラツキがあり、特に優れている命令はわからなかった。
アライメントがずれている場合などでは、違いが出てくるかもしれない。