初めて使ったunsafeでポインタが一番効果があった
画像処理の内容は色の反転、RGBそれぞれの値を逆にするだけのもので
50だったら255-50=205にする
単純な軽い処理
15種類、基準になるのは処理1で
結果
ポインタを使ったら1.926秒が0.874秒
約2倍速くなった
使った画像は
なぜこの画像を選んだのかコレガワカラナイ
PC環境は
OS Windows 10 Home 64bit
CPUAMD PhenomⅡ X3 720 @3.0GHz
メモリ DDR2 PC-800 8GB
っていう組んでから10年目になるパソコン
ビルドの設定?
デザイン画面
基準になるコードはこれ
private void ColorReverce1()
{
var wb = new WriteableBitmap(OriginBitmap);
int w = wb.PixelWidth;
int h = wb.PixelHeight;
int stride = wb.BackBufferStride;
byte[] pixels = new byte[h * stride];
wb.CopyPixels(pixels, stride, 0);
long p;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
p = y * stride + x * 4;
//RGBを反転、アルファ値はそのまま
pixels[p] = (byte)(255 - pixels[p]);
pixels[p + 1] = (byte)(255 - pixels[p + 1]);
pixels[p + 2] = (byte)(255 - pixels[p + 2]);
}
}
wb.WritePixels(new Int32Rect(0, 0, w, h), pixels, stride, 0);
MyImage.Source = wb;
}
OriginBitmapが元になる画像でBitmapSource
PixelFormatはPbgra32
これからWriteableBitmapを作ってCopyPixelsからbyte配列を作って
画像処理でRGBを反転、最後に確認用にImageコントロールの
MyImageに表示して完了
これを100回繰り返した時間を計測
なので実際の処理時間は1/100になる
時間計測はStopwatch
↑普通↓ポインタ使用
バックバッファーってのは後の画面、処理用の画面ってとこかなあ
対するのはフロント、メインとかで前の画面で実際に見えている画面
処理の途中は見えない後で行って結果だけを表示する感じ
ポインタってのはメモリのアドレスみたいな感じ
そのデータの先頭のメモリのアドレス
今回だとbyte型のポインタを使うからbyte* ptrってすると
ptrがbyte型のポインタになるみたいで
配列のインデックスみたいにptr[0]ってすると先頭のアドレスから1byte分のデータになって、ptr[1]だと先頭から1byte分進んだアドレスにある1byte分のデータ
ptr[2]だと先頭から2byte分進んだアドレスにある1byte分のデータ
かなあ
WriteableBitmap.Lockメソッドでメモリのロック
OSはメモリに入っているデータを最適な状態にしようと移動しているらしい
ハードディスクのデフラグみたいな感じかしら
処理中に勝手に移動されたら困るから、その対象から一時的に外して移動されないようにするのがWriteableBitmapのLockメソッドで、これを使った後は必ずUnlockメソッドを実行してもとに戻す
ポインタを使う前準備
プロジェクトのビルドのモードをunsafeってのにする必要があるみたいで
/unsafe (unsafe モードの有効化) (C#)
https://msdn.microsoft.com/ja-jp/library/ct597kb0%28v=vs.80%29.aspx?f=255&MSPPError=-2147217396
ソリューションエクスプローラーで
アプリの名前あたりを右クリックのメニューでプロパティ
プロパティ画面
これでコードでunsafeってのが使えるようになって
unsafeが使えるとポインタが使えるようになる
もしこうしないでunsafeって書いても
最初はこれがなんのことかわからなかった
処理2
530行目でp = y * stride + x * 4;
ってしてたのを
527行目とで分けた、少し計算量が減ったはずなんだけどタイムは変わらずだった
1.926秒が1.882秒
処理3
処理1のx,yの2重になっていたループを1重ループに変更
1.926秒が1.784秒
1割弱速くなった
ループ処理の合計は同じなんだけど速くなったから
forは減らしたほうがいいみたい
処理4
処理3のRGBの各値変更のところをforにしたら遅くなった
1.784秒が1.927秒
処理5
処理1のポインタ版+ビット反転版
1.926秒が0.786秒
2倍以上速くなった
ビット反転~を付けるとビット反転
p[0]が0000 0000だったときに~を付けて~p[0]にすると
~p[0]は1111 1111になる
ちょうど色の反転に使えたのでこれも初めて使ってみた
処理6
つまり処理1のポインタ版
1.926秒が0.874秒
画像処理でポインタを使うときはこれが基本になりそう
ビット反転版より遅いけど何回か計測すると逆転することもあったので
普通の引き算もビット反転も変わらないかもと、このときは思っていた
処理7
0.874秒→0.889秒
ほとんど変わらないのでこれは使う意味がない
処理8
0.874秒→0.973秒
なぜか遅くなった
でもこれはもっと重たい画像処理なら処理6より速くなるはず
…だと思う
処理9
0.874秒→0.898秒
速くなると思ったけど変わらず、意外
処理10
0.878秒→1.405秒
思った以上に遅くなった
PixelFormat.Pbgra32はアルファ値もあるけど
色反転の処理ではアルファ値は変更したくないのでifで判定して弾いている
その処理が重たいみたい、回数が多いからねえ
でもこれでパラレル版にできる、パラレルなら速くなるはず
処理11
1.405秒→2.209秒
速くなると思ったのに遅くなった、しかもかなり遅い
アルファ値がじゃま、ifをなくしたい
処理12
アルファ値がじゃまならPixelFormatを変更すればいいじゃない
FormatConvertedBitmapを使ってBgr24に変更して
色変反転処理を軽くしたら
2.209秒→1.938秒
速くなったけど…遅い
PixelFormat変換部分が重たいみたい
処理13
0.874秒→1.264秒と遅くなった
処理6ではWriteableBitmapのBackBufferからのポインタだったのをもとのBitmapSourceからCopyPixelsで得たbyte配列のポインタに変更しただけ
なんだけど遅くなったねえ
CopyPixelsと最後のBitmapSource.Createが重たいのかなあ
処理14
0.898秒→0.723秒
今回試した中で最速になった!
普通の引き算よりビット反転のほうが速いのかなあ
処理15
0.973秒→0.849秒
やっぱりビット反転のほうが速いみたい
DebugとReleaseと32bitと64bit
このモードの違いで処理時間がかなり変わるのに気づいた
多少は違うだろうなあと思っていたけど多少じゃなくて
5倍以上差が出るのもあった
できたときはRelease+x64でいいんだけど、作っている途中はどんな設定にしたらいいのかなあ、いままでビルドの設定を気にしたことなかったからさっぱりわからんけどCPUもOSも64bitだから32bit優先はないなあ
完走した感想
- ポインタ使ったほうが速い
- ビット反転のほうが引き算より速い
- forでのループ総数がおなじなら2重より1重のほうが速い
- 今回みたいに軽い画像処理ならパラレルは使わなくてもいい
コード
参照したところ
WriteableBitmapの画素をポインタから操作する - schima.hatenablog.com今回のコードはこちらを見よう見まねで書いた
http://schima.hatenablog.com/entry/20100918/1284817562
.NETによる画像処理の高速化Tips:unsafe編(改稿:2015/11/08)WPFじゃないけどbyte配列のポインタの使い方はこちらから
https://qiita.com/Nuits/items/da8c11e5b284ad6cb90a
関連記事
2018/3/26
処理速度比較、画像の使用色数を数える、重複なしのリストのHashSetも速いけど配列+ifも速かった ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15430276.html
2018/3/15
Parallelクラスを使ってもっと速く減色 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15413665.html