PascalではNVENCが10bit深度とかyuv444などでのエンコードに対応していて、それなりの速度でエンコードできるので、バンディング低減もできるといいかなあ、ということでCUDAで追加してみた。
更新内容
[共通]・バンディング低減フィルタを追加。
・パフォーマンス分析(--vpp-perf-monitor)ができなくなっていたのを修正。[NVEncC]・--avcuvidを使用すると、--cropが正しく反映されない場合があったのを修正。
NVEncにバンディング低減 (CUDA)
以前、
AVX2とかを使って高速化したものをCUDAを使ってNVEncで再現してみた。
パラメータは、Aviutl版とおなじようにできるようにしたつもり。NVEnc.auoでは「フィルタ/その他」から、NVEncCではコマンド(--vpp-deband)で指定できる。
NVEnc.auo
NVEncC
--vpp-deband [<param1>=<value>][,<param2>=<value>][...]
パラメータ range=<int> (default=15, 0-127)
ぼかす範囲です。この範囲内の近傍画素からサンプルを取り、ブラー処理を行います。
mode=<int> (default=1, 0-2)
設定値:0
周辺1画素を参照し、元の画素値を維持したまま処理を行います。
設定値:1
周辺1画素とその点対称画素の計2画素を参照し、ブラー処理を行います。
設定値:2
周辺2画素とその点対称画素の計4画素を参照し、ブラー処理を行います。
thre=<int> (一括設定)
thre_y=<int> (default=15, 0-31)
thre_cb=<int> (default=15, 0-31)
thre_cr=<int> (default=15, 0-31)
y,cb,cr 各成分の閾値です。この値が高いと階調飛びを減らす一方で、細かい線などが潰れます。
dither=<int> (一括設定)
dither_y=<int> (default=15, 0-31)
dither_c=<int> (default=15, 0-31)
y, cb & crのディザの強さ。
seed=<int>
乱数シードの変更 (default=1234)
blurfirst (default=off)
ブラー処理を先にすることでディザ強度を減らしつつ、階調飛びが多い素材での効果を上げます。
全体的に副作用が強くなり細かい線が潰れやすくなります。
rand_each_frame (default=off)
毎フレームシード値を変更します。
例: --vpp-deband range=31,dither=12,rand_each_frame
効果
入力がまあこんな感じだとして…
ややrangeを広めにかけるとこのぐらい (--vpp-deband range=31,dither=12,rand_each_frame)
まあ、強すぎとか弱すぎとかはひとそれぞれだろうし、出力が8bitで圧縮率を高めにすると再発しちゃったりするけど、それなりにちゃんと効果はあるみたい。
処理速度
環境i7 5960X 8C/16T
Core 3.9GHz / Uncore 3.6GHz
DDR4-2666, 4ch, 32GB
GTX1060 + 382.33ドライバ
測定--vpp-perf-monitor
fullHDを1枚処理する平均速度 (206フレームの平均)
パラメータは乱数生成の有無を除きデフォルト
| 毎フレーム乱数生成 |
なし | あり |
yuv420 | 8bit | 196.8 us | 374.3 us |
10bit | 243.3 us | 454.4 us |
yuv444 | 8bit | 350.9 us | 540.3 us |
10bit | 437.8 us | 655.5 us |
そのほかのCUDAフィルタの速度 (リンク先はすべてyuv420 8bitの結果)
とまあ、
前にやったAviutlでのAVX2版とかと比べても、だいぶ速いなあ、という感じ。
AviutlのYC48はデータサイズでいうとyuv444 10bitと同じpixelあたり6byteなのだけど、fullHDのYC48に対して5960X 8C/16T @ 4.2GHzが1.51ms、一方今回のが乱数生成なしで437.8us = 0.44msなので、まあ3倍以上速い。
CUDAは、処理対象のデータ構造が比較的単純であれば、とりあえずGPUで動くだけのコードはわりとあっさり書けるので、そういう意味ではSIMDよりかは楽な場合もある。CUDAが辛くなってくるのは、以下のような場合。
・そもそも並列度の低い計算には向かない
・レジスタスピルすると性能がガタ落ちする(cc3.x/5.xでだいぶ起こりにくくなったけど)
・処理ループが長く分割しにくいとかFP64とかだとレジスタを使いまくって詰む
・クラスとかポインタを駆使した複雑なデータ構造には向かない
→ (Unified Memory使うしかない)
・CPUとGPUの間でそれなりのサイズのデータをやりとりしようとした瞬間、
異常に遅くなる (PCIe実効帯域でない)
・これを解決しようとすると、pinnedメモリとかstreamとかeventとか
使い始めて異常に面倒なコードになる
・総和演算がどう見ても黒魔術 (やってることはわかるんだけど…)
これ、ひとつも当てはまらないようにするのは結構難しいかもだけど、幸いにしてNVEncのフィルタ処理だと、このいずれにも当てはまらないので、比較的楽だったりする。まあでもとりあえずはざっと、
・テクスチャキャッシュを使う
バンディング低減はランダムアクセスを大量にするのだけど、この速度を改善してくれる。
・ブロックサイズ調整
CUDAはブロック単位でSMに投入されるので、このブロックに入れるスレッド数が結構重要だったりする。メモリアクセスパターンと並列数とかを考えながら、手探りでチューニング。それなりに速度が変わる。
・なるべくFP32演算
GPUはIntよりFP32のほうが速いらしい。
・条件分岐はtemplateでコンパイル時に削除
GPUの条件分岐は、ようはSIMDでいうマスクなので分岐の両方の時間がかかるので、なくせるならなくしてしまう。欠点は、バイナリサイズが増えること…。
・#pragma unroll
GPUは、CPUならSIMD演算の合間にOoOでやってくれるちょっとした処理でも遅くなる。つまり、ループ処理自体も遅いので、展開してあげたほうが速くなることが多い。さらにループを展開することで定数にできる変数もあったりすると、そのぶん演算量が減るのでさらにおいしい。欠点は、バイナリサイズが増えること…。
・乱数はcurandのデバイスAPI
curandは結構お手軽に使えてなかなか便利。xorwowというのが一番速いらしいのでそれで。さすがにメルセンヌツイスターとかでなくてもいいと思う。
とかで、そんなにきっちりチューニングはしてないけど、ある程度の速さは出ていると思う。
ダウンロード>>ダウンロード (ミラー) >>OneDriveの調子がいまいちの時はミラー(GDrive)からどうぞ。同じものです。ソースはこちら>>