私が最速の画像サむズ倉曎をしたように。 パヌト2、SIMD

これは、私がどのように最適化に取り組み、最新のx86プロセッサヌで最速のサむズ倉曎を受け取ったかに぀いおの䞀連の蚘事の続きです。 各蚘事では、ストヌリヌの䞀郚を説明したすが、他の人にコヌドを最適化するようにプッシュしたいず考えおいたす。 前のシリヌズ


→ パヌト0
→ パヌト1、䞀般的な最適化


前回は、アプロヌチを倉曎せずに、平均で2.5倍の加速を埗たした。 今回は、SIMDアプロヌチを適甚しお、さらに3.5倍加速する方法を瀺したす。 もちろん、グラフィックス凊理にSIMDを䜿甚するこずはノりハりではなく、SIMDがこのために発明されたずさえ蚀えたす。 しかし、実際には、画像凊理タスクにもそれを䜿甚する開発者はほずんどいたせん。 たずえば、かなりよく知られおいる䞀般的なラむブラリImageMagickずLibGDは、SIMDを䜿甚せずに䜜成されおいたす。 これは、SIMDアプロヌチが客芳的にはより耇雑でクロスプラットフォヌムではないためず、情報がほずんどないためです。 基本を芋぀けるのは非垞に簡単ですが、詳现な資料や実際の問題の分析は十分ではありたせん。 Stack Overflowのこれから、文字通りすべおのささいなこずに぀いお倚くの質問がありたすデヌタをダりンロヌドする方法、アンパックする方法、パックする方法。 誰もが自分でコヌンを埋めなければならないこずがわかりたす。


SIMDずは


すでに基本に粟通しおいる堎合は、このセクションをスキップしおください。 SIMDは、単䞀の呜什、耇数のデヌタを衚したす。 このアプロヌチにより、耇数の同䞀の操䜜を1぀にたずめるこずができたす。 操䜜が実行されるデヌタセットはベクトルず呌ばれたす。



ほずんどの堎合、最新のプロセッサでは、SIMD呜什は察応するスカラヌず同じ数のクロックサむクルで実行されたす。 ぀たり 理論的には、SIMDに切り替えるず、䜿甚するデヌタ型ず呜什セットに応じお、2、4、8、16、さらには32倍の加速が期埅できたす。 実際には、それは異なっお出おきたす。 たず、ベクトル化されたコヌドでも、コヌドの䞀郚はスカラヌのたたです。 第二に、倚くの堎合、ベクトル挔算のために、デヌタを準備する必芁がありたすアンパックずパック。 原則ずしお、SIMDコヌドを蚘述するずき、デヌタのパックずアンパックは最も難しいこずです。 第䞉に、SIMD呜什は通垞の呜什の正確なコピヌではありたせん。䞀郚の操䜜には問題を適切に解決する特定の呜什があり、他のタスクには必芁な呜什がありたせん。 たずえば、最小倀ず最倧倀を芋぀けるために、条件付きゞャンプなしで動䜜する個別のSIMD呜什がありたす。 ただし、x86プロセッサには敎数ベクトルの陀算はありたせん。


より倚くの倀がベクトルレゞスタに配眮されたす。 デヌタの皮類ずレゞスタのサむズに䟝存したす。 SSEレゞスタは128ビットです。 たずえば、32ビット敎数で䜜業する堎合、4぀の倀が1぀のSSEレゞスタに収たりたす。 以䞋のデヌタタむプが䞻に利甚可胜です



すべおのベクトルレゞスタは同じであり、それらがどのタむプのデヌタであるかを知りたせん。 解釈は、レゞスタで動䜜する呜什のみに䟝存したす。 したがっお、ほずんどの呜什には、異なるタむプのデヌタを扱うためのバリ゚ヌションがありたす。 呜什は、1぀のタむプのデヌタを受信し、別のタむプを提䟛できたす。 アセンブラに粟通しおいる堎合、これは通垞のレゞスタがどのように機胜するかを幟分連想させたす。32ビットeaxレゞスタに䜕かを曞き蟌んでから、16ビットaxパヌツを操䜜できたす。


適切なコマンド拡匵機胜の遞択


最初のSIMD呜什は、Intel Pentium MMXプロセッサに登堎したした。 実際にMMX-これはチヌムの拡倧の名前です。 このキットは非垞に重芁だったため、Intelはそれをプロセッサの名前に取り入れたした。 MMXを䜿甚しお、2぀の画像の混合やスヌパヌサンプリングなどの簡単なアルゎリズムを曞いたこずがありたす。 Delphiで䜜成したしたが、MMXを䜿甚するには、䞋のレベルに移動しおアセンブラヌで挿入する必芁がありたした。


それ以来、プロセッサチヌムず関連する開発ツヌルの開発に぀いおはあたりフォロヌしおいたせん。 したがっお、最近SIMDを再び取り䞊げたずき、私はうれしい驚きを芚えたした。 いいえ、コンパむラは、倚少耇雑な堎合でもSIMD呜什を自動的に適甚するこずはできたせん。 そしお、圌が有胜であれば、圌は通垞、自分で曞いたSIMDコヌドよりも悪くなりたす。 しかし䞀方で、SIMDを䜿甚するには、アセンブラヌで蚘述する必芁がなくなり、すべおが特別な関数-組み蟌み関数で行われたす。


ほずんどの堎合、各組み蟌み関数は1぀の特定のプロセッサ呜什に察応しおいたす。 ぀たり 蚘述されたコヌドは非垞に効率的で、ハヌドりェアに近いものです。 しかし同時に、䜿い慣れた比范的安党なC構文を䜿甚しおコヌドを蚘述したす。 通垞どおり、組み蟌み関数が定矩されおいるヘッダヌファむルをむンクルヌドしたす。通垞は、特殊なデヌタ型を䜿甚しお倉数を宣蚀し、通垞の呌び出し関数ずしお定矩したす。 ぀たり、通垞のコヌドを蚘述したす。 少し䞍䟿ですが、SIMDデヌタ型では数孊挔算を䜿甚できないため、すべおの蚈算に組み蟌み関数を䜿甚する必芁がありたす。 倧たかに蚀うず、 ss0 + ss1曞くこずはできたせんadd_float(ss0, ss1) 関数の名前が発明されたしたしかできたせん。


SIMD拡匵機胜は倚数ありたす。 基本的に、プロセッサに新しい拡匵機胜が存圚するずいうこずは、すべおの先行機胜が存圚するこずを意味したす。 拡匵機胜の倖芳の時系列に埓っお、次の順序で配眮されたす。


MMX、SSE、SSE2、SSE3、SSSE3、SSE4.1、SSE4.2、AVX、AVX2、AVX-512


ご芧のずおり、このリストは印象的です。 すべおのマシンにすべおの拡匵機胜があるわけではありたせん。 博物通では、MMXのないラむブx86プロセッサのみを芋぀けるこずができたす。 SSE2は、64ビットプロセッサに必芁な拡匵機胜です。 最近では、ほがどこにでもありたす。 SSE4.2のサポヌトは、Nehalemアヌキテクチャ以降、どのプロセッサでも芋぀けるこずができたす。 2008幎から。 ただし、AVX2は、Haswellコア以降の非予算のIntelプロセッサヌでのみ䜿甚できたす。 2013幎以降、AMDでは2017幎にリリヌスされたRyzenプロセッサヌに登堎したした。 AVX-512は珟圚、Intel XeonおよびXeon Phiサヌバヌプロセッサでのみ利甚可胜です。


呜什セットの遞択は、コヌドの蚘述のパフォヌマンスず耇雑さ、およびプロセッサのサポヌトに䟝存したす。 開発者は、異なる呜什セットに察しおコヌドの実装を耇数䜜成する堎合がありたす。 SSE4.2ずAVX2の2぀を遞択したした。 私はこのように掚論したしたSSE4.2は、少なくずもパフォヌマンスに関心がある人なら誰でも心配する必芁のない基本セットです。たずえば、SSE2にすべおを実装したす。 AVX2は、少なくずも3幎に1回はハヌドりェアを倉曎するのが面倒ではない人向けです。 実装のために遞択したものが䜕であれ、遞択された呜什セットを備えた垂堎のプロセッサの数は増加するだけであるため、時間が経぀に぀れお、遞択はより正確になりたす。


SSE4の実装


最埌にコヌドに戻りたしょう。 CでSSE4.2を䜿甚するには、次の3぀のヘッダヌファむルを接続する必芁がありたす。


 #include <emmintrin.h> #include <mmintrin.h> #include <smmintrin.h> 

さらに、コンパむラフラグ-msse4指定する必芁がありたす。 Pythonモゞュヌル私たちのモゞュヌルなどの構築に぀いお話しおいる堎合は、コマンドラむンからこのフラグを盎接远加しお、アセンブリを耇雑にしないようにするこずができたす。


 $ CC="ccache cc -msse4" python ./setup.py develop 

最も包括的な組み蟌みリファレンスは、 Intel Intrinsics Guideにありたす。 優れた怜玢ずフィルタリングがあり、組み蟌み関数の説明は、察応する呜什、呜什の擬䌌コヌド、さらには最新䞖代のIntelプロセッサのクロックサむクルでの実行時間を瀺したす。 参考ずしお、これはナニヌクなものです。 しかし、このガむドの圢匏では、どのようなこずが起こるべきかに぀いおの䞀般的な状況を把握するこずはできたせん。


ベクトル化は、異なるデヌタに察する同じ操䜜のみに圹立ちたす。 この堎合、同じアクションが異なる画像チャンネルで実行されたす


 for (xx = 0; xx < imOut->xsize; xx++) { ss0 = 0.5; ss1 = 0.5; ss2 = 0.5; for (y = ymin; y < ymax; y++) { ss0 = ss0 + (UINT8) imIn->image[y][xx*4+0] * k[y-ymin]; ss1 = ss1 + (UINT8) imIn->image[y][xx*4+1] * k[y-ymin]; ss2 = ss2 + (UINT8) imIn->image[y][xx*4+2] * k[y-ymin]; } imOut->image[yy][xx*4+0] = clip8(ss0); imOut->image[yy][xx*4+1] = clip8(ss1); imOut->image[yy][xx*4+2] = clip8(ss2); } 

垂盎方向ず氎平方向の通過に぀いおも同様のコヌドがありたす。 䟿宜䞊、䞡方を別々の関数に配眮し、次のシグネチャを持぀2぀の関数のフレヌムワヌク内でのみSIMDを䜿甚したす。


 void ImagingResampleHorizontalConvolution8u( UINT32 *lineOut, UINT32 *lineIn, int xsize, int *xbounds, float *kk, int kmax ); void ImagingResampleVerticalConvolution8u( UINT32 *lineOut, Imaging imIn, int ymin, int ymax, float *k ); 

芚えおいるなら、前のパヌトで、画像の2、3、4チャンネルに察しお3぀の特別なケヌスを䜜成したした。 これは、チャネルを通る内郚ルヌプを取り陀き、同時に画像にないチャネルに察しお䞍必芁な蚈算を実行しないために必芁でした。 SIMDバヌゞョンでは、チャネルごずに実装を共有したせん。すべおの蚈算は垞に4぀のチャネルで実行されたす。 各ピクセルは4぀の32ビット浮動小数点数で衚され、正確に1぀のSSEレゞスタを占有したす。 はい、3チャンネル画像の堎合、4぀の操䜜はアむドル状態になり、2チャンネル画像の堎合は半分になりたす。 ただし、有甚なデヌタを䜿甚しおSSEレゞスタを可胜な限り駆動しようずするよりも、これに目を぀ぶる方が簡単です。



䞊蚘のコヌドをもう䞀床芋おください。 最初の段階では、バッテリヌには0.5の䞀定倀が割り圓おられたすが、これは結果を䞞めるために必芁です。 関数_mm_set1_*は、単䞀の浮動小数点倀をレゞスタ党䜓にロヌドするために䜿甚されたす。


 __m128 sss = _mm_set1_ps(0.5); 

通垞、関数名の最埌の郚分は、機胜するデヌタのタむプを瀺したす。 私たちの堎合、それは_psであり、パックドシングルを意味したす。


さらに、ピクセルを浮動小数点数のベクトルずしお䜿甚したい堎合は、ピクセルを䜕らかの方法でこの衚珟に倉換する必芁がありたす。 SSEには、8ビット倀を単粟床数倀に䞀床に倉換する呜什はありたせん。 _mm_cvtepi32_psがありたす。これは、32ビット敎数を単粟床数倀に倉換したすが、䜿甚する前に、8ビット数倀を32ビット数倀にアンパックする必芁がありたす。 これに_mm_cvtepu8_epi32関数_mm_cvtepu8_epi32䟿利です。 圌女は、アドレスをメモリ内の128ビット倀に枡す必芁がありたす。


 __m128i pix_epi32 = _mm_cvtepu8_epi32(*(__m128i *) &imIn->image32[y][xx]); __m128 pix_ps = _mm_cvtepi32_ps(pix_epi32); 

倀を読み蟌むずきに明瀺的に行う必芁があるSIMDコヌドの量に泚意しおください。 スカラヌコヌドでは、これは存圚したせん。コンパむラヌは、8ビット敎数にfloatを乗算するため、最初の敎数もfloatに倉換する必芁があるこずをコンパむラヌ自身が理解しおいたす。


1ピクセルのすべおのチャネルの係数は同じであるため、 _mm_set1_psが_mm_set1_psたす。


 __m128 mmk = _mm_set1_ps(k[y - ymin]); 

係数をチャネルで乗算し、バッテリヌに远加するこずは残りたす。


 __m128 mul = _mm_mul_ps(pix_ps, mmk); sss = _mm_add_ps(sss, mul); 

珟圚、 sssアキュムレヌタにはピクセルチャネルの倀がありたす。これは実際に範囲[0、255]を超えるこずができるため、䜕らかの方法でこれらの倀を制限する必芁がありたす。 前の蚘事のclip8関数を芚えおいたすか 2぀の条件付き遷移がありたした。 SIMDの堎合、プロセッサはすべおのデヌタに察しお同じコマンドを実行する必芁があるため、デヌタに応じお条件付きゞャンプを䜿甚するこずはできたせん。 しかし、実際には_mm_min_epi32および_mm_max_epi32があるため、さらに優れおい_mm_max_epi32 。 したがっお、倀を笊号付き32ビット敎数に倉換し、[0、255]内でそれらをトリミングしたす。


 __m128i mmmax = _mm_set1_epi32(255); __m128i mmmin = _mm_set1_epi32(0); __m128i ssi = _mm_cvtps_epi32(sss); ssi = _mm_max_epi32(mmmin, _mm_min_epi32(mmmax, ssi)); 

残念ながら、 _mm_cvtepu8_epi32には逆の呜什はありたせん。したがっお、必芁なバむトを最初に移動しおから、 _mm_cvtsi128_si32を䜿甚しおSSEレゞスタを汎甚レゞスタに倉換するこずより良い方法は_mm_cvtepu8_epi32たせん_mm_cvtsi128_si32 。


 __m128i shiftmask = _mm_set_epi8(-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,12,8,4,0); lineOut[xx] = _mm_cvtsi128_si32(_mm_shuffle_epi8(ssi, shiftmask)); 

shiftmaskマスクマスクでは、䞋䜍バむトが右に行くこずに泚意しおshiftmask 。 氎平パスの堎合、すべおがたったく同じで、ピクセルのロヌド順序のみが倉曎されたす。隣接するピクセルは行からロヌドされ、隣接する行からはロヌドされたせん。


すべおの準備ができたした。テストを実行しお結果を確認したす。


 Scale 2560×1600 RGB image to 320x200 bil 0,01151 s 355.87 Mpx/s 161.4 % to 320x200 bic 0,02005 s 204.27 Mpx/s 158.7 % to 320x200 lzs 0,03421 s 119.73 Mpx/s 137.2 % to 2048x1280 bil 0,04450 s 92.05 Mpx/s 215.0 % to 2048x1280 bic 0,05951 s 68.83 Mpx/s 198.3 % to 2048x1280 lzs 0,07804 s 52.49 Mpx/s 189.6 % to 5478x3424 bil 0,18615 s 22.00 Mpx/s 215.5 % to 5478x3424 bic 0,24039 s 17.04 Mpx/s 210.5 % to 5478x3424 lzs 0,30674 s 13.35 Mpx/s 196.2 % 

コミット8d0412bの結果。


2.5倍から3倍に成長 3チャンネルのRGB画像でテストしおいるこずを思い出させおください。したがっお、この堎合の3倍の加速は基準ず芋なすこずができたす。


適切な梱包


以前のバヌゞョンは、スカラヌコヌドからコピヌされたほが1察1でした。 さらに、パックずアンパックのために、SSEの最新バヌゞョンに登堎した関数_mm_cvtepu8_epi32 、 _mm_max/min_epi32 、 _mm_shuffle_epi8 。 明らかに、人々は䜕らかの圢でこれらのタスクず以前のバヌゞョンのSSEに察凊したした。 実際、 _mm_pack*および_mm_unpack*デヌタをパック/アンパックするための䞀連の関数がありたす。 ここで解凍があたり圹に立たない堎合 _mm_cvtepu8_epi32私たちの目的に適しおいたす、梱包を倧幅に簡玠化できたす。 倀をシフトおよびトリミングするために定数が䞍芁になるように単玔化するために私たちはmmmax 、 mmmin 、およびshiftmaskに぀いお話しおいる。


実際には、パッキング関数_mm_packs _mm_packs*は、 _mm_packs_epi32などの名前の文字sで瀺されるように、飜和状態で実行されたす。 飜和ずは、倉換䞭に倉数の倀が新しい型の制限を超えた堎合、この型では極端なたたになるこずを意味したす。 たずえば、16ビットの笊号付き敎数から8ビットの笊号なしぞの倉換を行う堎合、倀257は255に倉換され、-3は0に倉換されたす。パッケヌゞ化関数は同時に倀をシフトし、範囲倖になるこずを防ぎたす。


 __m128i ssi = _mm_cvtps_epi32(sss); ssi = _mm_packs_epi32(ssi, ssi); ssi = _mm_packus_epi16(ssi, ssi); lineOut[xx] = _mm_cvtsi128_si32(ssi); 

この最適化は加速を䞎えたせんが、矎しく芋え、远加の定数を必芁ずしたせん。 コミットb17cdc9を監芖したす。


AVXレゞスタ


犬がズボンを着おいた堎合、圌女はそれをこのようにするのでしょうか、それずもそうしたすか



たた、レゞスタに2倍のデヌタが含たれおいた堎合はどうなりたすか AVXの仕組みを知ったずき、すぐにこの写真を思い出したした。 䞀芋、AVXの指瀺は奇劙で非論理的に芋えたす。 これは「SSEのように、たった2倍」ずいうだけでなく、ある皮のあいたいなロゞックを持っおいたす。 すでに䜿甚したのず同じ混合呜什を芋おください。 SSEバヌゞョンの擬䌌コヌドは次のずおりです。


 FOR j := 0 to 15 i := j*8 IF b[i+7] == 1 dst[i+7:i] := 0 ELSE index[3:0] := b[i+3:i] dst[i+7:i] := a[index*8+7:index*8] FI ENDFOR 

AVXの堎合、カりンタは単に15から31に増加するだけであるず仮定するのは論理的です。しかし、AVXバヌゞョンの擬䌌コヌドは非垞に耇雑です。


 FOR j := 0 to 15 i := j*8 IF b[i+7] == 1 dst[i+7:i] := 0 ELSE index[3:0] := b[i+3:i] dst[i+7:i] := a[index*8+7:index*8] FI IF b[128+i+7] == 1 dst[128+i+7:128+i] := 0 ELSE index[3:0] := b[128+i+3:128+i] dst[128+i+7:128+i] := a[128+index*8+7:128+index*8] FI ENDFOR 

AVXはSSEの2倍ではなく、2 SSEです ぀たり、AVXレゞスタを䞀察のベクトルずしお芋る必芁がありたす。 そしお、ほずんどのチヌムのこれらのベクトルは、盞互䜜甚したせん。 AVXコマンドの擬䌌コヌドをもう䞀床芋おください。最初のブロックは䞋䜍128ビットでのみ動䜜し、2番目のブロックは䞊䜍128ビットでのみ動䜜するこずがはっきりずわかりたす。 たた、䞊䜍バむトが䞋䜍バむトになるように、たたはその逆に混合するこずはできたせん。 さらに、この呜什では、分離は厳密ではありたせん。シフトする方法を瀺すレゞスタは、䞊郚ず䞋郚を異なる方法でシフトできたす。 そしお、䞡方の郚分の操䜜の匕数が同じであるこずがよくありたす。 擬䌌コヌド_mm256_blend_epi16䟋を次に瀺し_mm256_blend_epi16 。


 FOR j := 0 to 15 i := j*16 IF imm8[j%8] dst[i+15:i] := b[i+15:i] ELSE dst[i+15:i] := a[i+15:i] FI ENDFOR dst[MAX:256] := 0 

jは15に反埩され、マスクは8を法ずするバむトimm8[j%8]から取埗されるこずに泚意しおください。 ぀たり レゞスタの䞊郚ず䞋郚は垞に同じマスクを持ちたす。 開梱ず梱包は䟝然ずしお倚くの問題であり、䞊郚ず䞋郚で独立しお発生したす。


 PACK_SATURATED(src[127:0]) { dst[15:0] := Saturate_Int32_To_UnsignedInt16 (src[31:0]) dst[31:16] := Saturate_Int32_To_UnsignedInt16 (src[63:32]) dst[47:32] := Saturate_Int32_To_UnsignedInt16 (src[95:64]) dst[63:48] := Saturate_Int32_To_UnsignedInt16 (src[127:96]) RETURN dst[63:0] } dst[63:0] := PACK_SATURATED(a[127:0]) dst[127:64] := PACK_SATURATED(b[127:0]) dst[191:128] := PACK_SATURATED(a[255:128]) dst[255:192] := PACK_SATURATED(b[255:128]) dst[MAX:256] := 0 


結果の䞋䜍ビットは、入力パラメヌタヌの䞋䜍ビットのみに基づいお蚈算されたす。 そしお、䞊郚のものは䞊郚のものにのみ基づいおいたす。 私はこの芏則に぀いおの出兞で明瀺的に蚀及しおいたせんが、その理解によりSSEコヌドのAVXぞの移怍が倧幅に簡玠化されたす。


AVXコマンド


AVXコマンド自䜓ずSSEを区別するものがありたす。 最初のパヌトの最埌で説明したデヌタ䟝存関係の問題を芚えおいたすか 圌女はAVXで録音されたした。 もちろん、スカラヌ呜什の動䜜を修正するこずはすでに䞍可胜ですが、SSEからAVXに切り替えるずきに同じ間違いを防ぐこずができたす。 各AVX呜什の擬䌌コヌドでは、最埌に次の行がありたす。


 dst[MAX:256] := 0 

これは、AVXが将来の䞖代512ビット以䞊のレゞスタを䜿甚しお既存のコマンドの動䜜を決定するこずを意味したす。 しかし、それだけではありたせん。 VEXオペコヌドシステムは、AVXコマンドの゚ンコヌドに䜿甚されたす。 AVXには、SEX呜什でさえVEXで゚ンコヌドする機胜が含たれおいたす。 この方法でコヌディングされたSSE呜什は、最䞊䜍ビットがリセットされるずいう保蚌も受け取りたす。 SSEたたはその逆の埌にAVXコマンドを䜿甚するず、玄100ティックのペナルティがあるず聞いたこずがあるかもしれたせん。 幞いなこずに、このペナルティはVEXで゚ンコヌドされたSSEコマンドには適甚されたせん。 -mavxフラグを指定しお組み蟌み関数を䜿甚する-mavxコンパむラヌは新しい圢匏で呜什を生成したす。 悪いニュヌスは、コヌドが-mavxでコンパむルされ、SSEコマンドを含むが、AVXコマンドを含たない堎合、VEXで゚ンコヌドされ、AVXのないプロセッサヌでは機胜しないこずです。 ぀たり 同じ圢匏のアセンブリモゞュヌル内で、叀い圢匏のSSE呜什ずAVX呜什を䜿甚するこずはできたせん 。


 if (is_avx_available()) { resample_avx(); } else { resample_sse(); } 

-mavxフラグがある-mavx resample_sse()関数から-mavxコヌドはAVXなしではプロセッサで開始されず、このフラグresample_avx()ないresample_avx()関数からのコヌドはコンパむルresample_avx()たせん。


AVX2、垂盎通路


これたで、SIMDぞの転送は非垞に簡単でした。4぀の浮動小数点数ずしお衚される1぀のピクセルが1぀のSSE4レゞスタに収たるからです。 しかし、AVX2では、䞀床に8぀の浮動小数点倀、぀たり2ピクセルを凊理する必芁がありたす。 しかし、1぀のレゞスタに取り蟌むピクセルはどれですか ここでも、ズボンを着おいる犬の写真を挿入したいず思いたす。 フレヌムがどのように芋えるか、たずえば氎平たたみ蟌みを思い出させおください


 for (xx = 0; xx < xsize; xx++) { xmin = xbounds[xx * 2 + 0]; xmax = xbounds[xx * 2 + 1]; for (x = xmin; x < xmax; x++) { __m128i pix = lineIn[x]; __m128 mmk = k[x - xmin]; //    } } 

たずえば、ラむン内の隣接ピクセルを取埗するこずができたす lineIn[x]およびlineIn[x + 1] 、これは最も明癜なオプションです。 ただし、これらのピクセルに察しお、異なる係数 k[x - xmin]およびk[x - xmin + 1] を準備する必芁がありたす。 たた、xmaxからxminたでの距離は奇数になる可胜性があり、最埌のピクセルを蚈算するには、SSEコヌドずAVXコヌドを組み合わせる必芁がありたす。


隣接する行でピクセルを取埗できたす lineIn1[x]およびlineIn2[x] 。 ただし、ピクセルを別々にロヌドおよびアンロヌドする必芁があるため、あたり䟿利ではありたせん。


実際、どの方法にもいく぀かの長所ず短所がありたす。 率盎に蚀っお、氎平通路をAVX2に転送するこずはあたり䟿利ではありたせん。 もう䞀぀は垂盎です 圌を芋お


 for (xx = 0; xx < imIn->xsize; xx++) { for (y = ymin; y < ymax; y++) { __m128i pix = image32[y][xx]; __m128 mmk = k[y - ymin]; //    } } 

ラむンimage32[y][xx]およびimage32[y][xx + 1]の隣接ピクセルを取埗でき、それらは同じ係数を持ちたす。 内郚サむクルが完了するず、バッテリヌは隣接する2぀のピクセルの結果になりたす;パックするこずも難しくありたせん。 ぀たり、すべおの__m128プレフィックスを__m256に、 _mm_を_mm256_倉曎するだけで、コヌドを曞き換えるこずができたす。 本圓に異なるのは、最埌に_mm256_castsi256_si128および_mm_storel_epi64を䜿甚するこずだけです。 1぀はnoopで、型キャストだけです。 そしお、2番目は64ビット倀をレゞスタからメモリに保存したす。


 Scale 2560×1600 RGB image to 320x200 bil 0.01162 s 352.37 Mpx/s -0,9 % to 320x200 bic 0.02085 s 196.41 Mpx/s -3,8 % to 320x200 lzs 0.03247 s 126.16 Mpx/s 5,4 % to 2048x1280 bil 0.03992 s 102.61 Mpx/s 11,5 % to 2048x1280 bic 0.05086 s 80.53 Mpx/s 17,0 % to 2048x1280 lzs 0.06563 s 62.41 Mpx/s 18,9 % to 5478x3424 bil 0.15232 s 26.89 Mpx/s 22,2 % to 5478x3424 bic 0.19810 s 20.68 Mpx/s 21,3 % to 5478x3424 lzs 0.23601 s 17.36 Mpx/s 30,0 % 

コミット86fe8a2の結果。


初めおパフォヌマンスがわずかに䜎䞋したした。 これは枬定゚ラヌではありたせん。なぜなら 結果は非垞に安定しおいたす。 埌でその理由を説明したす。 それたでの間、ゲむンは䞻に増加であり、画像の倧幅な枛少ではないこずは明らかです。 掚枬するのは難しいこずではありたせんが、これは垂盎方向の通過が氎平方向の通過の埌に行われ、最終画像のサむズが倧きくなるず効果が倧きくなるために起こりたす。 䞀般的に、この状況は非垞にポゞティブです。


AVX2氎平パス


氎平方向のパスの堎合、2぀の隣接するピクセルを連続しお取埗する方が䟿利です。 次に、それらのために異なる係数を準備する必芁がありたす。


 __m256 mmk = _mm256_set1_ps(k[x - xmin]); mmk = _mm256_insertf128_ps(mmk, _mm_set1_ps(k[x - xmin + 1]), 1); 

最埌に、256ビットレゞスタの䞊郚の結果を䞋郚の結果に远加する必芁がありたす。


 __m128 sss = _mm_add_ps( _mm256_castps256_ps128(sss256), _mm256_extractf128_ps(sss256, 1)); 

 Scale 2560×1600 RGB image to 320x200 bil 0,00918 s 446.18 Mpx/s 26,6 % to 320x200 bic 0,01490 s 274.90 Mpx/s 39,9 % to 320x200 lzs 0,02287 s 179.08 Mpx/s 42,0 % to 2048x1280 bil 0,04186 s 97.85 Mpx/s -4,6 % to 2048x1280 bic 0,05029 s 81.44 Mpx/s 1,1 % to 2048x1280 lzs 0,06178 s 66.30 Mpx/s 6,2 % to 5478x3424 bil 0,16219 s 25.25 Mpx/s -6,1 % to 5478x3424 bic 0,19996 s 20.48 Mpx/s -0,9 % to 5478x3424 lzs 0,23377 s 17.52 Mpx/s 1,0 % 

コミットfd82859の結果。


ここでも、以前のバヌゞョンず比范しお䞀郚のサむズでわずかな損倱が芋られたす。 しかし、䞡方の最適化を合蚈するず、6〜50増加したした。 平均しお、AVX2バヌゞョンはSSE4バヌゞョンより25高速です。


それはたくさんですか、それずも少しですか もちろん、新しい䞀連の指瀺からさらに倚くを取埗したいず思いたす。 , 100% , 50% . , , .


, . , . Intel Core i5-4258U. , , .


, : . i5-4258U 2.4 2.9 . — , . — , . , . , , , . Intel Power Gadget . , SSE4- 2.9 . AVX2-, 2.75 . AVX2-, 2.6 . ぀たり , AVX2-, . AVX2- , - . AVX2 . 䜕が蚀えたすか , AVX2 .


, Xeon E5-2680 v2 ( Haswell, ) — AVX2- , , .



Source: https://habr.com/ru/post/J326900/


All Articles