私が最速の画像サむズ倉曎をしたように。 パヌト1、䞀般的な最適化

パむロットパヌトでは、タスクに぀いおできる限り詳现に説明したした。 ストヌリヌは長くお意味のないものであるこずが刀明したした。コヌドは1行もありたせんでした。 しかし、タスクを理解しなければ、最適化を行うこずは非垞に困難です。 もちろん、䞀郚の手法は、手元のコヌドのみで適甚できたす。 たずえば、キャッシュの蚈算、分岐を枛らしたす。 しかし、私には、タスクを理解しないずできないこずがあるようです。 これにより、人ず最適化コンパむラが区別されたす。 したがっお、手動の最適化は䟝然ずしお倧きな圹割を果たしたす。コンパむラにはコヌドのみがあり、人はタスクを理解しおいたす。 コンパむラは、倀「4」が十分にランダムであるず刀断するこずはできたせんが、人は刀断できたす。



実生掻のPillowラむブラリで畳み蟌み法を䜿甚しお画像のサむズ倉曎操䜜を最適化するこずに焊点を圓おるこずを思い出させおください。 数幎前に行った倉曎に぀いおお話したす。 しかし、これは単語ごずの繰り返しではありたせん。最適化はナレヌションに郜合の良い順序で説明されたす。 これらの蚘事のために、バヌゞョン2.6.2からリポゞトリに別のブランチを䜜成したした。この瞬間から物語が続きたす。


テスト䞭


読むだけでなく、自分で実隓したい堎合は、 pillow-perfテストパッケヌゞが圹立ちたす。


#        $ sudo apt-get install -y build-essential ccache \ python-dev libjpeg-dev $ git clone -b opt/scalar https://github.com/uploadcare/pillow-simd.git $ git clone --depth 10 https://github.com/python-pillow/pillow-perf.git $ cd ./pillow-simd/ # ,     $ git checkout bf1df9a #    Pillow $ CC="ccache cc" python ./setup.py develop # -   $ ../pillow-perf/testsuite/run.py scale -n 3 

Pillowは倚くのモゞュヌルで構成されおおり、むンクリメンタルにコンパむルする方法がわからないため、ccacheナヌティリティを䜿甚しお再アセンブリを倧幅に高速化したす。 Pillow-perfを䜿甚するず、倚くの操䜜をテストできたすが、 scale関心がありたす。 -n 3は、操䜜の開始回数を蚭定したす。 コヌドは遅いですが、眠りに萜ちないように、より小さな数を取るこずができたす。 起動時のパフォヌマンスは次のずおりです。


 Scale 2560×1600 RGB image to 320x200 bil 0.08927 s 45.88 Mpx/s to 320x200 bic 0.13073 s 31.33 Mpx/s to 320x200 lzs 0.16436 s 24.92 Mpx/s to 2048x1280 bil 0.40833 s 10.03 Mpx/s to 2048x1280 bic 0.45507 s 9.00 Mpx/s to 2048x1280 lzs 0.52855 s 7.75 Mpx/s to 5478x3424 bil 1.49024 s 2.75 Mpx/s to 5478x3424 bic 1.84503 s 2.22 Mpx/s to 5478x3424 lzs 2.04901 s 2.00 Mpx/s 

コミットbf1df9aの結果。


これらの結果は、バヌゞョン2.6の公匏ベンチマヌクで埗られた結果ずわずかに異なりたす 。 これにはいく぀かの理由がありたす。


  1. 公匏ベンチマヌクでは、GCC 5.3で64ビットUbuntu 16.04を䜿甚しおいたす。 GCC 4.8で32ビットUbuntu 14.04を䜿甚したす。GCC4.8では、これらの最適化をすべお初めお実行したした。 蚘事の終わりに、その理由が明らかになりたす。
  2. 蚘事では、最適化に関係しないがパフォヌマンスに圱響するバグを修正するコミットから話を始めたす。

コヌド構造


興味のあるコヌドのほずんどは、 ImagingStretch関数のAntialias.cファむルにありたす。 この関数のコヌドは、3぀の郚分に分けるこずができたす。


 //  if (imIn->xsize == imOut->xsize) { //   } else { //   } 

前に蚀ったように、2぀のパスで画像の畳み蟌みのサむズ倉曎を行うこずができたす。最初は画像の幅のみ、2番目は高さ、たたはその逆です。 1぀の呌び出しのImagingStretch関数は、どちらか䞀方だけを実行できたす。 ここでは、各サむズ倉曎䞭に実際に2回呌び出されるこずがわかりたす 。 この関数は、䞀般的なプロロヌグを実行し、パラメヌタヌに応じお、この操䜜たたはその操䜜を実行したす。 反埩コヌドこの堎合はプロロヌグを削陀するためのかなり珍しいアプロヌチ。


内郚では、䞡方のパスはほが同じように芋え、凊理の方向が倉わるように調敎されおいたす。 簡朔にするために、1぀だけを瀺したす。


 for (yy = 0; yy < imOut->ysize; yy++) { //   if (imIn->image8) { //     8  } else { switch(imIn->type) { case IMAGING_TYPE_UINT8: //      8  case IMAGING_TYPE_INT32: //     32  case IMAGING_TYPE_FLOAT32: //     float } } 

Pillowでサポヌトされるいく぀かのピクセル衚珟圢匏に分岐しおいたすシングルチャンネル8ビットグレヌスケヌル、マルチチャンネル8ビットRGB、RGBA、LA、CMYK、その他、シングルチャンネル32ビット、フロヌト。 これが最も䞀般的な画像圢匏であるため、いく぀かの8ビットチャネルのルヌプの本䜓に興味がありたす。


最適化1キャッシュを効果的に䜿甚する


䞊蚘で2぀のパスは䌌おいるず述べたしたが、それらの間には明らかな違いがありたす。 垂盎通路を芋おください


 for (yy = 0; yy < imOut->ysize; yy++) { //   for (xx = 0; xx < imOut->xsize*4; xx++) { //    //    imOut->image8[yy][xx] } } 

氎平通路


 for (xx = 0; xx < imOut->xsize; xx++) { //   for (yy = 0; yy < imOut->ysize; yy++) { //    //    imOut->image8[yy][xx] } } 

最終画像の列は、内偎のルヌプの垂盎方向の通路ず、氎平方向の行を繰り返したす。 氎平パスは、プロセッサキャッシュにずっお重倧な問題です。 内偎のルヌプの各ステップで、以䞋の1行にアクセスしたす。これは、前のステップで必芁な倀ずは異なるメモリからの倀が芁求されるこずを意味したす。 畳み蟌みサむズが小さい堎合、これは良くありたせん。 実際、最新のプロセッサでは、プロセッサがRAMから芁求できるキャッシュラむンは垞に64バむトです。 ぀たり、畳み蟌みに関係するピクセルが16ピクセル未満の堎合、デヌタの䞀郚がRAMからキャッシュに浪費されたす。 ここで、サむクルが逆になり、次のピクセルがラむンの䞋で厩壊せず、同じラむンの次のピクセルが厩壊するこずを想像しおください。 その埌、必芁なピクセルのほずんどが既にキャッシュにありたす。


このようなコヌドの線成の2番目のマむナス芁因は、畳み蟌みの長い行぀たり、倧幅な枛少で珟れたす。 実際には、隣接する畳み蟌みでは、元のピクセルが非垞に倧きく亀差するため、このデヌタがキャッシュに残っおいればいいでしょう。 しかし、䞊から䞋に移動するず、叀い畳み蟌みのデヌタは、新しい畳み蟌みのデヌタによっお埐々にキャッシュから远い出され始めたす。 その結果、完党な内郚ルヌプが通過し、次の倖郚ステップが開始されるず、キャッシュに䞊郚の行が衚瀺されなくなり、すべお䞋郚の行に眮き換わりたす。再びメモリから取埗する必芁がありたす。 そしお、䞋䜍のものになるず、キャッシュ内のすべおがすでに䞊䜍のものに眮き換えられおいたす。 サむクルが刀明し、その結果、キャッシュに必芁なデヌタが含たれなくなりたす。


なぜ迂回はそうなのですか 䞊蚘の擬䌌コヌドでは、䞡方の堎合の2行目が畳み蟌みの係数の蚈算であるこずがわかりたす。 垂盎方向の通過の堎合、係数は最終画像の行 yy倀のみに䟝存し、氎平方向の通過の堎合は珟圚の列 xx倀に䟝存したす。 ぀たり、氎平の通路では、単玔に2぀のサむクルを亀換するこずはできたせん。係数の蚈算は、xxサむクル内にある必芁がありたす。 内郚ルヌプ内の係数のカりントを開始するず、すべおのパフォヌマンスが䜎䞋したす。 特に、䞉角関数があるLanczosフィルタヌを䜿甚しお係数を蚈算する堎合。


各ステップで係数を蚈算するこずはできたせんが、それでも1列のすべおのピクセルで同じです。 そのため、すべおの列に぀いおすべおの係数を事前に蚈算でき、内偎のルヌプではすでに蚈算されおいたす。 やっおみたしょう。


コヌドには、係数甚のメモリ割り圓おがありたす。


 k = malloc(kmax * sizeof(float)); 

ここで、このような配列の配列が必芁です。 しかし、単玔化するこずは可胜です-平らなメモリを割り圓おお、2次元のアドレス指定を゚ミュレヌトしたす。


 kk = malloc(imOut->xsize * kmax * sizeof(float)); 

たた、 xminずxmaxをxxに䟝存するどこかに栌玍する必芁がありたす。 それらの䞋で、再蚈算しないように配列も䜜成したす。


 xbounds = malloc(imOut->xsize * 2 * sizeof(float)); 

たた、ルヌプ内でww倀が䜿甚されたす。これは、畳み蟌み倀を正芏化するために必芁です。 ww = 1 / ∑k [x]。 あなたはそれを党く保存するこずはできず、畳み蟌みの結果ではなく、係数自䜓を正芏化したす。 ぀たり、係数を蚈算した埌、もう䞀床係数を調べお合蚈で割る必芁がありたす。その結果、すべおの係数の合蚈は1になりたす。


 k = &kk[xx * kmax]; for (x = (int) xmin; x < (int) xmax; x++) { float w = filterp->filter((x - center + 0.5) * ss); k[x - (int) xmin] = w; ww = ww + w; } for (x = (int) xmin; x < (int) xmax; x++) { k[x - (int) xmin] /= ww; } 

これで、最終的に90°の走査でピクセルを拡匵できたす。


 //    for (yy = 0; yy < imOut->ysize; yy++) { for (xx = 0; xx < imOut->xsize; xx++) { k = &kk[xx * kmax]; xmin = xbounds[xx * 2 + 0]; xmax = xbounds[xx * 2 + 1]; //    //    imOut->image8[yy][xx] } } 

 Scale 2560×1600 RGB image to 320x200 bil 0.04759 s 86.08 Mpx/s 87.6 % to 320x200 bic 0.08970 s 45.66 Mpx/s 45.7 % to 320x200 lzs 0.11604 s 35.30 Mpx/s 41.6 % to 2048x1280 bil 0.24501 s 16.72 Mpx/s 66.7 % to 2048x1280 bic 0.30398 s 13.47 Mpx/s 49.7 % to 2048x1280 lzs 0.37300 s 10.98 Mpx/s 41.7 % to 5478x3424 bil 1.06362 s 3.85 Mpx/s 40.1 % to 5478x3424 bic 1.32330 s 3.10 Mpx/s 39.4 % to 5478x3424 lzs 1.56232 s 2.62 Mpx/s 31.2 % 

コミットd35755cの結果。


4列目は前のオプションず比范した加速を瀺し、衚の䞋にはコミットぞのリンクがあり、そこで行われた倉曎が明確に衚瀺されたす。


最適化2出力制限


コヌドでは、いく぀かの堎所に次の構造がありたす。


 if (ss < 0.5) imOut->image[yy][xx*4+b] = (UINT8) 0; else if (ss >= 255.0) imOut->image[yy][xx*4+b] = (UINT8) 255; else imOut->image[yy][xx*4+b] = (UINT8) ss; 

これは、蚈算結果が8ビットを超える堎合、[0、255]内のピクセル倀の制限です。 実際、すべおの正の畳み蟌み係数の合蚈は1より倧きくなり、すべおの負の畳み蟌み係数の合蚈はれロより小さくなりたす。 そのため、特定の゜ヌスむメヌゞでは、オヌバヌフロヌが発生する可胜性がありたす。 このオヌバヌフロヌは、茝床の突然の倉化を補正した結果であり、゚ラヌではありたせん。


コヌドを芋おください。 1぀の入力倉数ssず1぀の出力imOut->image[yy] 、その倀は耇数の堎所に割り圓おられたす。 悪いこずは、浮動小数点数が比范されるこずです。 すべおを敎数に倉換しおから比范する方が高速です。最終的には結果党䜓が必芁だからです。 合蚈で、この関数を取埗したす。


 static inline UINT8 clip8(float in) { int out = (int) in; if (out >= 255) return 255; if (out <= 0) return 0; return (UINT8) out; } 

䜿甚法


 imOut->image[yy][xx*4+b] = clip8(ss); 

これにより、パフォヌマンスは向䞊したすが、わずかではありたすが。


 Scale 2560×1600 RGB image to 320x200 bil 0.04644 s 88.20 Mpx/s 2.5 % to 320x200 bic 0.08157 s 50.21 Mpx/s 10.0 % to 320x200 lzs 0.11131 s 36.80 Mpx/s 4.2 % to 2048x1280 bil 0.22348 s 18.33 Mpx/s 9.6 % to 2048x1280 bic 0.28599 s 14.32 Mpx/s 6.3 % to 2048x1280 lzs 0.35462 s 11.55 Mpx/s 5.2 % to 5478x3424 bil 0.94587 s 4.33 Mpx/s 12.4 % to 5478x3424 bic 1.18599 s 3.45 Mpx/s 11.6 % to 5478x3424 lzs 1.45088 s 2.82 Mpx/s 7.7 % 

コミット54d3b9dの結果。


ご芧のずおり、この最適化は、りィンドりが小さく、出力解像床が高いフィルタヌに倧きな効果をもたらしたした唯䞀の䟋倖は320x200バむリニアですが、理由は蚀えたせん。 実際、フィルタヌりィンドりが小さく、最終的な解像床が倧きいほど、最適化した倀のクリッピングによるパフォヌマンスぞの寄䞎が倧きくなりたす。


最適化3反埩回数が䞀定のルヌプを回す


再び氎平方向のステップをよく芋るず、最倧4぀のネストされたサむクルをカりントできたす。


 for (yy = 0; yy < imOut->ysize; yy++) { // ... for (xx = 0; xx < imOut->xsize; xx++) { // ... for (b = 0; b < imIn->bands; b++) { // ... for (x = (int) xmin; x < (int) xmax; x++) { ss = ss + (UINT8) imIn->image[yy][x*4+b] * k[x - (int) xmin]; } } } } 

出力画像の各行ず各列が繰り返され぀たり、各ピクセル、元の画像の折りたたたれる各ピクセルが内郚で繰り返されたす。 しかし、 bずは䜕ですか bは画像チャンネルの繰り返しです。 明らかに、チャンネルの数は関数党䜓で倉化せず、4を超えるこずはありたせん画像がPillowに保存される方法のため。 したがっお、考えられるケヌスは4぀だけです。 たた、シングルチャネルの8ビット画像が異なる方法で保存されるずいう事実を考えるず、3぀のケヌスがありたす。 したがっお、2぀、3぀、および4぀のチャネルに察しお、3぀の別々の内郚サむクルを䜜成できたす。 そしお、適切な数のチャネルに分岐したす。 あたりスペヌスをずらないように、3チャンネルの堎合のコヌドのみを瀺したす。


 for (xx = 0; xx < imOut->xsize; xx++) { if (imIn->bands == 4) { //   4  } else if (imIn->bands == 3) { ss0 = 0.0; ss1 = 0.0; ss2 = 0.0; for (x = (int) xmin; x < (int) xmax; x++) { ss0 = ss0 + (UINT8) imIn->image[yy][x*4+0] * k[x - (int) xmin]; ss1 = ss1 + (UINT8) imIn->image[yy][x*4+1] * k[x - (int) xmin]; ss2 = ss2 + (UINT8) imIn->image[yy][x*4+2] * k[x - (int) xmin]; } ss0 = ss0 * ww + 0.5; ss1 = ss1 * ww + 0.5; ss2 = ss2 * ww + 0.5; imOut->image[yy][xx*4+0] = clip8(ss0); imOut->image[yy][xx*4+1] = clip8(ss1); imOut->image[yy][xx*4+2] = clip8(ss2); } else { //       } } 

xxルヌプたで、そこで停止しおブランチを1レベル䞊に移動するこずはできたせん。


 if (imIn->bands == 4) { for (xx = 0; xx < imOut->xsize; xx++) { //   4  } } else if (imIn->bands == 3) { for (xx = 0; xx < imOut->xsize; xx++) { //   3  } } else { for (xx = 0; xx < imOut->xsize; xx++) { //       } } 

 Scale 2560×1600 RGB image to 320x200 bil 0.03885 s 105.43 Mpx/s 19.5 % to 320x200 bic 0.05923 s 69.15 Mpx/s 37.7 % to 320x200 lzs 0.09176 s 44.64 Mpx/s 21.3 % to 2048x1280 bil 0.19679 s 20.81 Mpx/s 13.6 % to 2048x1280 bic 0.24257 s 16.89 Mpx/s 17.9 % to 2048x1280 lzs 0.30501 s 13.43 Mpx/s 16.3 % to 5478x3424 bil 0.88552 s 4.63 Mpx/s 6.8 % to 5478x3424 bic 1.08753 s 3.77 Mpx/s 9.1 % to 5478x3424 lzs 1.32788 s 3.08 Mpx/s 9.3 % 

コミット95a9e30の結果。


同様のこずが垂盎通路に぀いおも可胜です。 珟圚、そのようなコヌドがありたす


 for (xx = 0; xx < imOut->xsize*4; xx++) { /* FIXME: skip over unused pixels */ ss = 0.0; for (y = (int) ymin; y < (int) ymax; y++) ss = ss + (UINT8) imIn->image[y][xx] * k[y-(int) ymin]; ss = ss * ww + 0.5; imOut->image[yy][xx] = clip8(ss); } 

チャネルに個別の反埩はありたせん。代わりに、 xxは幅に4を掛けお反埩したす。぀たり、 xxは画像内の数に関係なく各チャネルを通過したす。 コメントのFIXMEは、これを修正する必芁があるずだけ蚀っおいたす。 同じ方法で修正されたす-元の画像の異なる数のチャンネルのコヌドを分岐するこずによっお。 ここではコヌドを提䟛したせん。コミットぞのリンクは以䞋にありたす。


 Scale 2560×1600 RGB image to 320x200 bil 0.03336 s 122.80 Mpx/s 16.5 % to 320x200 bic 0.05439 s 75.31 Mpx/s 8.9 % to 320x200 lzs 0.08317 s 49.25 Mpx/s 10.3 % to 2048x1280 bil 0.16310 s 25.11 Mpx/s 20.7 % to 2048x1280 bic 0.19669 s 20.82 Mpx/s 23.3 % to 2048x1280 lzs 0.24614 s 16.64 Mpx/s 23.9 % to 5478x3424 bil 0.65588 s 6.25 Mpx/s 35.0 % to 5478x3424 bic 0.80276 s 5.10 Mpx/s 35.5 % to 5478x3424 lzs 0.96007 s 4.27 Mpx/s 38.3 % 

コミットf227c35の結果。


ご芧のように、氎平方向の通路はパフォヌマンスを向䞊させお写真を瞮小し、垂盎方向の通路はパフォヌマンスを向䞊させたした。


最適化4敎数カりンタヌ


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

最も内偎のルヌプを芋るず、倉数ymaxおよびymax floatずしお宣蚀ymaxおいるが、各ステップで敎数にymaxおいるこずがわかりたす。 さらに、ルヌプの倖偎では、 floor関数ずceil関数を䜿甚しお倀を割り圓おたす。 ぀たり、実際には敎数は垞に倉数に栌玍されたすが、䜕らかの理由で敎数ずしお浮動小数点ずしお宣蚀されたす。 xminずxmaxに぀いおxmin同じこずがxmaxたす。 亀換しお枬定したす。


 Scale 2560×1600 RGB image to 320x200 bil 0.03009 s 136.10 Mpx/s 10.9 % to 320x200 bic 0.05187 s 78.97 Mpx/s 4.9 % to 320x200 lzs 0.08113 s 50.49 Mpx/s 2.5 % to 2048x1280 bil 0.14017 s 29.22 Mpx/s 16.4 % to 2048x1280 bic 0.17750 s 23.08 Mpx/s 10.8 % to 2048x1280 lzs 0.22597 s 18.13 Mpx/s 8.9 % to 5478x3424 bil 0.58726 s 6.97 Mpx/s 11.7 % to 5478x3424 bic 0.74648 s 5.49 Mpx/s 7.5 % to 5478x3424 lzs 0.90867 s 4.51 Mpx/s 5.7 % 

コミット57e8925の結果。


最終行為ずボス


私は認める、私は結果に非垞に満足しおいた。 コヌドを平均2.5倍オヌバヌクロックできたした。 さらに、この高速化を実珟するために、ラむブラリナヌザヌは远加の機噚をむンストヌルする必芁がなく、以前ず同じように同じプロセッサの同じコアでサむズ倉曎が実行されたす。 必芁なのは、Pillowのバヌゞョンをバヌゞョン2.7にアップグレヌドするこずだけでした。


しかし、リリヌス2.7以前にはただ時間があり、動䜜するはずのサヌバヌで新しいコヌドをチェックするのに焊りたした。 コヌドを移怍し、コンパむルしたしたが、最初は䜕かを台無しにしたず思いたした。


 Scale 2560×1600 RGB image 320x200 bil 0.08056 s 50.84 Mpx/s 320x200 bic 0.16054 s 25.51 Mpx/s 320x200 lzs 0.24116 s 16.98 Mpx/s 2048x1280 bil 0.18300 s 22.38 Mpx/s 2048x1280 bic 0.31103 s 13.17 Mpx/s 2048x1280 lzs 0.43999 s 9.31 Mpx/s 5478x3424 bil 0.75046 s 5.46 Mpx/s 5478x3424 bic 1.22468 s 3.34 Mpx/s 5478x3424 lzs 1.70451 s 2.40 Mpx/s 

コミット57e8925の結果。 別のマシンで受信され、比范には関䞎したせん。


黙っお 結果は、最適化前ずほずんど同じです。 すべおを10回チェックし、適切なコヌドが機胜するこずを確認するために印刷したした。 それは枕や環境​​からの副䜜甚ではなく、30行の最小限の䟋でも違いが再珟されたした。 Stack Overflowに぀いお質問したずころ、最終的に明らかなパタヌンを芋぀けるこずができたした。64ビットプラットフォヌム甚のGCCでコンパむルされた堎合、コヌドはゆっくり実行されたした。 そしお、それはたさにロヌカルUbuntaずサヌバヌの違いでしたロヌカルには32ビットがありたした。


さお、Muruの栄光、私はクレむゞヌではありたせん。これはコンパむラの本圓のバグです。 さらに、バグはGCC 4.9で修正されたしたが、GCC 4.8はUbuntu 14.04 LTSに含たれおいたした。これは圓時関連がありたした。぀たり、ほずんどの堎合、ラむブラリのほずんどのナヌザヌによっおむンストヌルされたした。 これを無芖するこずは䞍可胜でした。最適化は、それが䜜られた生産物を含め、倧倚数の人にずっおうたくいかない堎合は良いこずです。 SOの質問を曎新し、Twitterで叫びたした。 V8゚ンゞンず最適化の倩才の開発者の1人であるVyacheslav Egorovが圌のもずに来お、問題の根底にたどり着き、解決策を芋぀けたした。


問題の本質を理解するには、プロセッサの歎史ず珟圚のアヌキテクチャを詳しく調べる必芁がありたす。 昔々、x86プロセッサは浮動小数点数を扱う方法を知らなかったため、x87呜什のセットを備えたコプロセッサが発明されたした。 圌は䞭倮凊理装眮ず同じスレッドから呜什を実行したしたが、マザヌボヌドに別のデバむスずしおむンストヌルされたした。 すぐに、コプロセッサヌは䞭倮のコンピュヌタヌに組み蟌たれ始め、物理的には1぀のデバむスになりたした。 どれくらい短いか、䞀連のSSEストリヌミングSIMD拡匵呜什呜什が3番目のPentiumに登堎したした。 ずころで、SIMD呜什に぀いおは、䞀連の蚘事の第2郚になりたす。 その名前にもかかわらず、SSEには浮動小数点数を操䜜するためのSIMDコマンドだけでなく、スカラヌ蚈算甚の同等のコマンドも含たれおいたした。 ぀たり、SSEにはx87セットを耇補する䞀連の呜什が含たれおいたしたが、゚ンコヌド方法が異なり、動䜜がわずかに異なりたす。


ただし、コンパむラヌは浮動小数点蚈算甚のSSEコヌドを生成するこずを急いでいたせんでしたが、叀いx87スむヌトを匕き続き䜿甚したした。 結局のずころ、昔から組み蟌たれおいるx87ずは異なり、プロセッサのSSEを保蚌する人はいたせんでした。 64ビットプロセッサモヌドの登堎により、すべおが倉わりたした。 64ビットモヌドでは、SSE2呜什のセットが必須になりたした。 ぀たり、x86甚の64ビットプログラムを䜜成しおいる堎合、少なくずもSSE2呜什を䜿甚できたす。 これは、コンパむラが64ビットモヌドで浮動小数点蚈算甚のSSE呜什を生成するずきに䜿甚するものです。 これはベクトル化ずは䜕の関係もないこずを思い出させおください;私たちは通垞のスカラヌ蚈算に぀いお話しおいたす。


これがたさに私たちのケヌスで起こるこずです。32ビットモヌドず64ビットモヌドでは異なる呜什セットが䜿甚されたす。 しかし、これたでのずころ、これは、最新のSSEコヌドが埓来のx87スむヌトよりも数倍遅い理由を説明しおいたせん。 この珟象を説明するには、プロセッサが呜什を実行する方法を正確に把握する必芁がありたす。


むかしむかし、プロセッサヌは本圓に指瀺に埓いたした。 圌らは呜什を取り、それを解読し、完党に実行し、結果を蚀われたずころに眮きたした。 プロセッサはかなり銬鹿だった。 最新のプロセッサは、はるかにスマヌトで耇雑であり、数十の異なるサブシステムで構成されおいたす。 䞊列凊理を行わない1぀のコアでも、プロセッサは1クロックサむクルで䞀床に耇数の呜什を実行したす。 さたざたな段階で発生したす。䞀郚の呜什はただデコヌド䞭、䞀郚はキャッシュからのリク゚スト、䞀郚は算術ブロックに転送されたす。 各プロセッササブシステムは、独自の郚分に関䞎しおいたす。 これはコンベアず呌ばれたす。



図では、異なるサブシステムが異なる色で瀺されおいたす。 コマンドの実行には4〜5クロックサむクルが必芁ですが、パむプラむンのおかげで、各クロックサむクルで1぀の新しいコマンドが遞択され、1぀のコマンドが実行を完了したす。


コンベアはより効率的に動䜜し、より均䞀に充填され、アむドル状態のサブシステムが少なくなりたす。 プロセッサには、パむプラむンの最適な充填を蚈画するサブシステムもありたす。呜什をスワップし、1぀の呜什を耇数に分割し、耇数を1぀に結合したす。


, — . - , .



, 2 1. . .


, , , :


 Instruction: cvtsi2ss xmm, r32 dst[31:0] := Convert_Int32_To_FP32(b[31:0]) dst[127:32] := a[127:32] 

32 . , dst - a , , xmm , dst a — , 96 , . . , , , . , 32- float. , , . .


, . cvtsi2ss , xorps . . , , , , xorps + cvtsi2ss - :


 dst[31:0] := Convert_Int32_To_FP32(b[31:0]) dst[127:32] := 0 

, GCC 4.8 , , . , , , . 64- .


 Scale 2560×1600 RGB image 320x200 bil 0.02447 s 167.42 Mpx/s 320x200 bic 0.04624 s 88.58 Mpx/s 320x200 lzs 0.07142 s 57.35 Mpx/s 2048x1280 bil 0.08656 s 47.32 Mpx/s 2048x1280 bic 0.12079 s 33.91 Mpx/s 2048x1280 lzs 0.16484 s 24.85 Mpx/s 5478x3424 bil 0.38566 s 10.62 Mpx/s 5478x3424 bic 0.52408 s 7.82 Mpx/s 5478x3424 lzs 0.65726 s 6.23 Mpx/s 

81fc88e . .


, , . , , , . ImageMagick : 64- , GCC 4.9 40% . , SSE.


: 2, SIMD



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


All Articles