PMD_MT 高速化版+5 gatherの速度とか

Broadwell/Skylakeのgatherは(条件によっては)速いのかもしれない、という話。

一方、Haswellのgatherは残念な子。

更新内容



結論から言うと、BroadwellとSkylakeで、useExp=オンの時の処理を高速化したというのがPMD_MT 高速化版+5です。



測定環境



速度の測定をした環境は以下の通り。

OSWin10 x64Win10 x64
CPUi7 4770K (4C/8T)i7 6700K (4C/8T)
CPU世代HaswellSkylake
Core4.0GHz4.3GHz
UnCore4.0GHz4.1GHz
キャッシュL3=8MBL3=8MB
メモリDDR3-1600, 2chDDR4-2933, 2ch
CL8-8-8-24-216-18-18-34-2




useExpの時のpmdの計算



pmd_mtでノイズ除去するとき、データ構造と重み関数を以下のように定義して、

typedef struct {
short y; // 画素(輝度 )データ ( 0 ~ 4096 )
short cb; // 画素(色差(青))データ ( -2048 ~ 2048 )
short cr; // 画素(色差(赤))データ ( -2048 ~ 2048 )
} PIXEL_YC;

PIXEL_YC *dst; //出力フレーム
PIXEL_YC *src; //入力フレーム
PIXEL_YC *gref; //srcにガウスぼかしを適用したフレーム

int func_weight(int x) {
const double range = 4.0;
const double strength2 = strength/100.0;
const double inv_threshold2 = 1.0 / pow(2.0,threshold*0.1);
return int((1<<16)/range * strength2
* exp(-x*x * inv_threshold2));
}


あるピクセルは以下のように計算する。(cb, crも同様)

dst->y = src->y + (short)((
((src-max_w)->y - src->y)*func_weight((gref-max_w)->y - gref->y) + //上
((src-1 )->y - src->y)*func_weight((gref-1)->y - gref->y) + //左
((src+1 )->y - src->y)*func_weight((gref+1)->y - gref->y) + //右
((src+max_w)->y - src->y)*func_weight((gref+max_w)->y - gref->y) //下
) >>16 );


というわけで、1ピクセル計算するのにfunc_weightは4回x3 (y, cb,cr)=12回呼ばれることになり、結構重い。さらにuseExpの場合、その名の通りfunc_weightの中にexpが使用されている。expを12回もやっていると、まあ、だいぶ重い計算になる。

もともとpmd_mtはこの重い重み計算を回避するため、最初にとりうるすべてのxについてあらかじめ重み計算を行ってテーブルを作っておき、あとはテーブル参照するだけというコードになっていた。


int *pmdp; //重みの計算結果を格納したテーブル

dst->y = src->y + (short)((
((src-max_w)->y - src->y)*pmdp[(gref-max_w)->y - gref->y] + //上
((src-1 )->y - src->y)*pmdp[(gref-1)->y - gref->y] + //左
((src+1 )->y - src->y)*pmdp[(gref+1)->y - gref->y] + //右
((src+max_w)->y - src->y)*pmdp[(gref+max_w)->y - gref->y] //下
) >>16 );


こうすることで、重み計算を毎回行わなくて済み、だいぶ高速化できる。



useExpのSIMD化と表引き



PMD_MT 高速化版では、このPMDの計算をSIMD化することで高速化した。ただ、表引きのところは問題で、AVXまでは対応する命令がなく、結局その部分は非SIMDで行っていた。

ループ構造を取り外して、AVX2のサンプルコードはこんな感じ。

PIXEL_YC *src; //入力フレーム
PIXEL_YC *gau; //srcにガウスぼかしを適用したフレーム

__declspec(align(32)) int16_t diffBuf[64]; //一時バッファ
__declspec(align(32)) int expBuf[64]; //一時バッファ

__m256i ySrcUpperDiff, ySrcLowerDiff, ySrcLeftDiff, ySrcRightDiff;
__m256i yGauUpperDiff, yGauLowerDiff, yGauLeftDiff, yGauRightDiff;

//上下左右の差分をSIMD計算
getDiff(src, max_w, ySrcUpperDiff, ySrcLowerDiff, ySrcLeftDiff, ySrcRightDiff);
getDiff(gau, max_w, yGauUpperDiff, yGauLowerDiff, yGauLeftDiff, yGauRightDiff);

//差分の計算結果を一度バッファに格納
_mm256_store_si256((__m256i *)(diffBuf + 0), yGauUpperDiff);
_mm256_store_si256((__m256i *)(diffBuf + 16), yGauLowerDiff);
_mm256_store_si256((__m256i *)(diffBuf + 32), yGauLeftDiff);
_mm256_store_si256((__m256i *)(diffBuf + 48), yGauRightDiff);

//表引きを非SIMD実行
for (int i = 0; i < _countof(expBuf); i += 16) {
expBuf[i+ 0] = pmdp[diffBuf[i+ 0]];
expBuf[i+ 1] = pmdp[diffBuf[i+ 1]];
expBuf[i+ 2] = pmdp[diffBuf[i+ 2]];
expBuf[i+ 3] = pmdp[diffBuf[i+ 3]];
expBuf[i+ 4] = pmdp[diffBuf[i+ 8]];
expBuf[i+ 5] = pmdp[diffBuf[i+ 9]];
expBuf[i+ 6] = pmdp[diffBuf[i+10]];
expBuf[i+ 7] = pmdp[diffBuf[i+11]];
expBuf[i+ 8] = pmdp[diffBuf[i+ 4]];
expBuf[i+ 9] = pmdp[diffBuf[i+ 5]];
expBuf[i+10] = pmdp[diffBuf[i+ 6]];
expBuf[i+11] = pmdp[diffBuf[i+ 7]];
expBuf[i+12] = pmdp[diffBuf[i+12]];
expBuf[i+13] = pmdp[diffBuf[i+13]];
expBuf[i+14] = pmdp[diffBuf[i+14]];
expBuf[i+15] = pmdp[diffBuf[i+15]];
}

//表引きした重みをロード
__m256i yEUpperlo = _mm256_load_si256((__m256i *)(expBuf + 0));
__m256i yEUpperhi = _mm256_load_si256((__m256i *)(expBuf + 8));
__m256i yELowerlo = _mm256_load_si256((__m256i *)(expBuf + 16));
__m256i yELowerhi = _mm256_load_si256((__m256i *)(expBuf + 24));
__m256i yELeftlo = _mm256_load_si256((__m256i *)(expBuf + 32));
__m256i yELefthi = _mm256_load_si256((__m256i *)(expBuf + 40));
__m256i yERightlo = _mm256_load_si256((__m256i *)(expBuf + 48));
__m256i yERighthi = _mm256_load_si256((__m256i *)(expBuf + 56));

//取り出した重みを使用してSIMD計算を続行

まあ、あまり、速くない。



gather命令 (vpgatherdd)



さて、AVX2にはこの表引き(というか間接参照)をやる専用命令があって、


int *pDst, *pTable, *pIndex;
for (int i = 0; i < 8; i++) {
pDst[i] = pTable[pIndex[i]];
}

これを、

int *pTable;
__m256i yDst, yIndex;
yDst = _mm256_i32gather_epi32(pTable, yIndex, 4);

とやるとvpgatherdd命令1発でやってくれるらしい(注1)。

これがあると、先ほどの表引きコードがかなり簡単になって、


PIXEL_YC *src; //入力フレーム
PIXEL_YC *gau; //srcにガウスぼかしを適用したフレーム

__m256i ySrcUpperDiff, ySrcLowerDiff, ySrcLeftDiff, ySrcRightDiff;
__m256i yGauUpperDiff, yGauLowerDiff, yGauLeftDiff, yGauRightDiff;

//上下左右の差分をSIMD計算
getDiff(src, max_w, ySrcUpperDiff, ySrcLowerDiff, ySrcLeftDiff, ySrcRightDiff);
getDiff(gau, max_w, yGauUpperDiff, yGauLowerDiff, yGauLeftDiff, yGauRightDiff);

//差分を16bit->32bitに変換
__m256i yGauUpperDiffLo = cvtlo256_epi16_epi32(yGauUpperDiff);
__m256i yGauUpperDiffHi = cvthi256_epi16_epi32(yGauUpperDiff);
__m256i yGauLowerDiffLo = cvtlo256_epi16_epi32(yGauLowerDiff);
__m256i yGauLowerDiffHi = cvthi256_epi16_epi32(yGauLowerDiff);
__m256i yGauLeftDiffLo = cvtlo256_epi16_epi32(yGauLeftDiff);
__m256i yGauLeftDiffHi = cvthi256_epi16_epi32(yGauLeftDiff);
__m256i yGauRightDiffLo = cvtlo256_epi16_epi32(yGauRightDiff);
__m256i yGauRightDiffHi = cvthi256_epi16_epi32(yGauRightDiff);

//重みの表引き
__m256i yEUpperlo = _mm256_i32gather_epi32(pmdp, yGauUpperDiffLo, 4);
__m256i yEUpperhi = _mm256_i32gather_epi32(pmdp, yGauUpperDiffHi, 4);
__m256i yELowerlo = _mm256_i32gather_epi32(pmdp, yGauLowerDiffLo, 4);
__m256i yELowerhi = _mm256_i32gather_epi32(pmdp, yGauLowerDiffHi, 4);
__m256i yELeftlo = _mm256_i32gather_epi32(pmdp, yGauLeftDiffLo, 4);
__m256i yELefthi = _mm256_i32gather_epi32(pmdp, yGauLeftDiffHi, 4);
__m256i yERightlo = _mm256_i32gather_epi32(pmdp, yGauRightDiffLo, 4);
__m256i yERighthi = _mm256_i32gather_epi32(pmdp, yGauRightDiffHi, 4);

//取り出した重みを使用してSIMD計算を続行

おお、なんかすごそうじゃないか!

ところが、この速度をHaswellで測ると…

pmd_mt_haswell.png
処理対象: 1920x1080, 1024フレーム

あれー。gather使うとなんか遅くなる。

まあ、これはIntel自ら

pmd_mt_haswell.png

Haswellのgatherは遅くて、次頑張るからBroadwellも買ってね的なことを言ってるのでしょーがない。

Intel Architectures Optimization Manualにも「11.16.4 Considerations for Gather Instructions」で"Example 11-43 shows some of those situations where, use of VGATHER on Intel microarchitecture code name Haswell is unlikely to provide performance benefit"(Haswellだとこういう条件ではgather使うと遅いよ)とあって、なんかいろいろ書いてあるので、まあ、基本Haswellのgather命令は遅いということなのだろう。

Haswellのgatherは要らない子?

というわけで、昔、gatherを使うコードを書いたのだけど、無効化してました



それじゃSkylakeでは?



例のIntel Architectures Optimization Manualには、「11.16.4 Considerations for Gather Instructions」でBroadwellとSkylakeではgather命令がこんだけ速くなったアピールがされている。

実際計ってみると

pmd_mt_skylake.png
処理対象: 1920x1080, 1024フレーム

と、ちゃんと速くなる。素晴らしい。



というわけで、どうもBroadwellも速いらしいので、BroadwellとSkylakeではgather命令を使うようにして、useExp=オンの時の処理を高速化したというのがPMD_MT 高速化版+5です。



ダウンロード>>
ダウンロード (ミラー) >>

ソースはこちら



(注1) 実際には、maskを初期化するためvpcmpeqb ymm0, ymm0, ymm0とかが入るので、2命令になったりする


スポンサーサイト



コメントの投稿

非公開コメント

No title

404と出てダウンロードできませんでした。
配布は中止にされたのでしょうか?

Re: No title

単にリンクが死んでるだけでしたのでなおしました。

No title

返事が遅くなってごめんなさい。
たった今頂きました。ありがとうございました。

あと、最初にお伝えするべき事を忘れておりました。
いつも拝見させて頂いております。有益なソフトと情報の提供、とても感謝しております。

なんだか社交辞令っぽくてごめんなさい。
プロフィール

rigaya

Author:rigaya
アニメとか見たり、エンコードしたり。
連絡先: rigaya34589@live.jp
github twitter

最新記事
最新コメント
カテゴリ
月別アーカイブ
カウンター
検索フォーム
いろいろ
公開中のAviutlプラグインとかのダウンロード

○Aviutl 出力プラグイン
x264guiEx 3.xx
- x264を使用したH264出力
- x264guiExの導入紹介動画>
- x264guiExの導入
- x264guiExのエラーと対処方法>
- x264.exeはこちら>

x265guiEx
- x265を使用したH.265/HEVC出力
- x265guiExの導入>
- x265.exeはこちら>

QSVEnc + QSVEncC
- QuickSyncVideoによるHWエンコード
- QSVEnc 導入/使用方法>
- QSVEncCオプション一覧>

NVEnc + NVEncC
- NVIDIAのNVEncによるHWエンコード
- NVEnc 導入/使用方法>
- NVEncCオプション一覧>

VCEEnc + VCEEncC
- AMDのVCE/VCNによるHWエンコード
- VCEEnc 導入/使用方法>
- VCEEncCオプション一覧>

svtAV1guiEx
- SVT-AV1によるAV1出力
- svtAV1guiExの導入>
- SVT-AV1単体はこちら>

VVenCguiEx
- VVenCによるVVC出力
- VVenCguiExの導入>

ffmpegOut
- ffmpegを使用した出力
- ffmpegOutの導入>


○Aviutl フィルタプラグイン
自動フィールドシフト (ミラー)
- SSE2~AVX512による高速化版
- オリジナル: aji様

clfilters 
- OpenCLベースの複数のGPUフィルタ集
- 対応フィルタの一覧等はこちら

エッジレベル調整MT (ミラー)
- エッジレベル調整の並列化/高速化
- SSE2~AVX512対応
- オリジナル: まじぽか太郎様

バンディング低減MT (ミラー)
- SSE2~AVX512による高速化版
- オリジナル: まじぽか太郎様

PMD_MT
- SSE2~AVX512による高速化版
- オリジナル: スレ48≫989氏

透過性ロゴ (ミラー)
- SSE2~FMA3によるSIMD版
- オリジナル: MakKi氏

AviutlColor (ミラー)
- BT.2020nc向け色変換プラグイン
- BT.709/BT.601向けも同梱

○その他
Amatsukaze改造版
- AmatsukazeのAV1対応版

x264afs (ミラー)
- x264のafs対応版

aui_indexer (ミラー使い方>)
- lsmashinput.aui/m2v.auiの
 インデックス事前・一括生成

auc_export (ミラー使い方>)
- Aviutl Controlの
 エクスポートプラグイン版
 エクスポートをコマンドから

aup_reseter (ミラー)
- aupプロジェクトファイルの
 終了フラグを一括リセット

CheckBitrate (ミラー, 使い方, ソース)
- ビットレート分布の分析(HEVC対応)

チャプター変換 (使い方>)
- nero/appleチャプター形式変換

エッジレベル調整 (avisynth)
- Avisynth用エッジレベル調整

メモリ・キャッシュ速度測定
- スレッド数を変えて測定
- これまでの測定結果はこちら

○ビルドしたものとか
L-SMASH (ミラー)
x264 (ミラー)
x265 (ミラー)
SVT-AV1 (ミラー)

○その他
サンプル動画
その他

○読みもの (ミラー)
Aviutl/x264guiExの色変換
動画関連ダウンロードリンク集
簡易インストーラの概要

○更新停止・公開終了
改造版x264gui
x264guiEx 0.xx
RSSリンクの表示
リンク
QRコード
QR