ホーム > プログラミング

プログラミングのアーカイブ

C#でポインタとbyte配列の高速なやり取り

C#で、ポインタとバイト配列を相互にやり取りしたい事が多い。
例えば、画像の画素値を高速に得るためにBitmapData.Scan0を使いたいけど、byte[]に入れて色々処理したい時とか。
Marshal.Copyでいいじゃんと言われそうだけど、たとえばRGB画像のR画像だけ欲しい時、いちいち全部Marshal.Copyしてから扱うのは無駄に思える。
(実際は中の人がDMAを使ってるのか、すごく速い)

やり方は色々思いつくけど、どれが高効率かわからないので、実験してみた。
以下Marshalクラスを眺めたりして思いついた方法。IntPtrが指すRGB画像を色ごとにbyte[]にぶち込むのが目標とする。

  • managed
  • Marshal.Readbyte(…)を使う
  • Readbyte(ptr, ofs)でofsをインクリメント
  • アドレスをインクリメントして、Readbyte(new IntPtr(addr))する
  • Marshal.Copyで普通にやる
    • 全画像Copyして、配列で頑張る
    • 全画像Copyして、1色ずつ画像を完成させていく(Read画像とWrite画像が1つずつにできるので、キャッシュを期待)
    • Line毎にCopyしてみる
    • 画像を幾つかのパートに分けて、マルチスレッド化してLine毎にCopyしてみる
  • unmanaged
    • ptr.ToPointer()からfixed(byte* R_Image)にポインタインクリメントしながらコピー
    • ptr.ToPointer()からMarshal.AllockでコピーしてからMarshal.Copy
    • unmanagedmemorystreamからReadByteする

    結論から言うと、Marshal.ReadByteやUnmanagedMemoryStreamは遅かった。前述した通り、Marshal.Copyはとても速いので、これらの関数はこの用途には使えないね。ググってもほとんどMarshal.Copyしか出てこないのはこういう訳か。
    unsafeポインタ系はやっぱり速いけど、unsafeはあんまり使いたくないので、Line毎にMarshal.Copyして弄るっていう処理を複数スレッドでやることにする。

    以下、検証に使った関数たち
    続きを読む

    C#でGenericなJag配列の生成と多次元配列からの変換

    以前に、C#で配列の初期値つきの初期化について書きましたが、今回は更に多次元配列とかJag配列とかを簡単に作りたいっていう話。

    C#では多次元配列(a[2,3]みたいなやつ)も使えますが、僕はJag配列(a[2][3]みたいなやつ)の方が圧倒的に使用頻度が高いです。配列の配列っていう概念の方がわかりやすいし。
    できることとしては何が違うか知りませんが、引数の都合などでたまに多次元配列をJag配列化したいことがあります。
    最初は任意の次元の配列も再帰とかで書ける気がしたんだけど、実は書けないっぽい。

    ついでに、以前の記事のように初期値つきJag配列初期化についても考えたけど、こちらも再帰的に書けないきがしている。
    多次元の配列の初期化は、次元が増えるほど面倒なので、とりあえず妥協して、途中の次元まではArray型の配列型、最後の配列だけGenericに生成するようにして、初期化付きJag配列生成メソッドを書いてみた。

    public class k2kArray
    {
        public static T[][] ConvertToJag<T>(T[,] array)
        {
            int i, j;
            var ret = new T[array.GetLength(0)][];
            int l = array.GetLength(1);
     
            for (i = 0; i < ret.Length; ++i)
            {
                ret[i] = new T[l];
                for (j = 0; j < l; ++j) ret[i][j] = array[i, j];
            }
     
            return ret;
        }
     
        public static TElem[] Init<TElem>(TElem[] arr, TElem InitialValue) where TElem : struct { return Init<TElem>(arr, () => InitialValue); }
        public static TElem[] Init<TElem>(TElem[] arr, Func<TElem> InitElem) { return Init<TElem>(arr, (i) => InitElem()); }
        public static TElem[] Init<TElem>(TElem[] arr, Func<int, TElem> InitElem)
        {
            int i;
            for (i = 0; i < arr.Length; ++i) arr[i] = InitElem(i);
            return arr;
        }
     
        public static TElem[] CreateInit<TElem>(int Length, TElem InitialValue) where TElem : struct { return CreateInit<TElem>(Length, () => InitialValue); }
        public static TElem[] CreateInit<TElem>(int Length, Func<TElem> InitElem) { return CreateInit<TElem>(Length, (i) => InitElem()); }
        public static TElem[] CreateInit<TElem>(int Length, Func<int, TElem> InitElem) { return Init<TElem>(new TElem[Length], InitElem); }
     
        public static Array CreateJagArray<TElem>(Func<TElem> InitElem, params int[] Length)
        {
            return CreateJagArrayDelegate<TElem>(Length, Length.Length, InitElem)();
        }
     
        public static Func<Array> CreateJagArrayDelegate<TElem>(int[] Length, int Dimension, Func<TElem> InitElem)
        {
            if (Dimension == 0)
            {
                return () => CreateInit<TElem>(Length[Dimension], InitElem);
            }
            else
            {
                return () =>
                          CreateInit<Array>(Length[Dimension-1],
                                CreateJagArrayDelegate<TElem>(Length, Dimension - 1, InitElem)
                          );
            }
        }
     
    }

    なんか微妙。string[][]の初期化とかしたいんだけど、Array[]になっちゃう。。
    その後キャストするにしてもうまい方法が思いつかない。
    なんかいい方法ないかな。っていういつもの結論。

    C#で配列の初期値つきの初期化

    C#というか.NET Frameworkって配列に初期値を入れたい時に短い書き方がないよね。
    いちいちforループ書くのめんどくさいし。
    LINQあたりでうまく書けるのかもしれないけど、トリッキーなのもやだし、自分用ライブラリに初期化用メソッドを入れてみた。

    public class k2kArray
    {
    	public static TElem[] Init<TElem>(TElem[] arr, TElem InitialValue) where TElem : struct
    	{
    		return Init<TElem>(arr, () => InitialValue);
    	}
    	public static TElem[] Init<TElem>(TElem[] arr, Func<TElem> InitElem)
    	{
    		return Init<TElem>(arr, (i) => InitElem());
    	}
    	public static TElem[] Init<TElem>(TElem[] arr, Func<int, TElem> InitElem)
    	{
    		int i;
    		for (i = 0; i < arr.Length; ++i) arr[i] = InitElem(i);
    		return arr;
    	}
     
    	public static TElem[] CreateInit<TElem>(int Length, TElem InitialValue) where TElem : struct
    	{
    		return CreateInit<TElem>(Length, () => InitialValue);
    	}
    	public static TElem[] CreateInit<TElem>(int Length, Func<TElem> InitElem)
    	{
    		return CreateInit<TElem>(Length, (i) => InitElem());	
    	}
    	public static TElem[] CreateInit<TElem>(int Length, Func<int, TElem> InitElem)
    	{
    		return Init<TElem>(new TElem[Length], InitElem);
    	}
    }

    ついでに初期化された配列をnewする関数も。
    引数にデリゲートがある関数は参照型用。引数付きコンストラクタが呼びたい気分の時のために作った。
    ついでにIndexに依存する初期値を生成するラムダ式を引数に取れたら便利かと思ったのでそれも追加。
    使うときはこんな感じ。

    byte[] arr1 = k2kArray.CreateInit<byte>( 5, 0xFF );
    string[] arr2 = k2kArray.CreateInit<string>( 5, () => "初期値だよ" );
    string[] arr3 = k2kArray.CreateInit<string>( 5, (i) => string.Format("{0}番目", i) );

    見かけ的には、デリゲートよりnew()制約つけてnewするようにしたほうが良かったかも。余計なこともできるようにするか、シンプルにするか。
    ていうかデリゲートだと遅いかな。

    結局初期化式はあんまりスマートじゃなくなってしまった。
    なんかいい方法ないかな。

    ガーベッジコレクションまかせの画像処理

    画像処理書く時って、画像入れるメモリを確保したり開放したりするけど、ガーベジコレクションに慣れるとめんどくさい。

    OpenCVとか、色んなライブラリも、画像のメモリに関しては気にして書かなきゃいけなくて、C#でいうところのusingで囲得ないようなアルゴリズムも多くて、そのへんのデザインパターンがよくわからない僕としては難しいと思っちゃっています。

    速度とかわりとどうでもよかったり、テスト用のプログラムをサクっと書きたい時には、適当にManagedなbyte配列に画素値をぶっこんでごにょごにょして、適度にGC.Collect()しながら使うのが楽です。

    そのためのライブラリを作ってみたんだけど、メモリを放置できる快感を覚えてしまって、邪道と分かっていながら抜け出せない。。
    試しにつくって以来、結局いろんなフィルタとかを加えて、大きくなりつつある。
    そのうち公開します。

    GUI Toolkit

    おはようじむし
    最近linuxメインなので、GUI Toolkitに何使うか悩み中です。
    C#使いなのでMonoから呼べるラッパーがあると嬉しくて、ライセンスの緩いのがいいんだけど、そうするとwxWidgetしかない。
    でもドキュメント少ないし、とりあえずLGPLなGDI+使い中。
    なんか良いのないかなー。。。

    マルチプラットフォームで.NET

    最近Monoをいじっています。
    http://www.mono-project.com/Main_Page
    Visual Studioは高いのでMonoDevelopを使って、なんとなく書けるようになってきました。
    Gtk#のGUIがまだよくわからない状況。

    画像処理関係の処理をOpenCVSharpから使ってみて遊んでました。
    USBカメラからの取得とか顔認識とか、一通り遊んでみました。

    .NETで画像を扱う時って、画像フォーマットに悩む。特に動画として扱うとき。
    GDI+だとBitmapDataいちいち作るのめんどいし、リソース食う割に遅い気がする。
    Cで直にポインタ使えば処理は速いけど、できればManaged使いたいし、ManagedとUnmanageとのやりとりがややこしいから嫌。
    Managedな配列を使うと一番バランスいいかな。。
    でもOpenCVとかはIplImage.ImageDataでポインタ返してくるから、いちいちMarshal.Copyしなきゃいけなくなっちゃう気がする。
    WPFはGDI+より使いやすくて速いみたいだけど、勉強するのめんどくさい。
    うーん

    グラフ描画

    学生時代は特に、.NET Framework上で作ってるソフトでちょっとしたグラフを表示したい事がたまにあった。Office Web Componentsとかを使ったり、他のグラフ描画ソフトやExcelで楽なようなCSVを吐いて誤魔化したりしていたけど、どうも巧い方法が無かった。
    最近はもう自分である程度のものは作って持っとこうかと思い始めていたけど、久しぶりに調べてみると、なんかMicrosoftがコントロール作ってた!

    チャート・コントロールを使うには? − @IT

    まだドキュメント少ないけど、プロパティ名見ればいろいろわかるし、OWCでできていた事はたいていできそう。機能を端折って描画を速くした散布図(FastPoints)とかあるっぽい。

    3軸の3D散布図とか、ベクトルが描けるといいんだけどなー。まだいじってないからわからん。
    あとWPFで同じような機能欲しいな。。探せば有るかな。。なさそうだな。。
    この辺必要な人種はMicrosoftのFrameworkなんか使わないんだろうね、きっと。

    Stopwatchって普通ラップタイム計測できるよね

    でも、.NET FrameworkのSystem.Diagnostics.Stopwatchはちょっと不便。

    • ラップタイムが計測できない。
    • ElapsedMillisecondsプロパティがlong型で、精度が悪い。
    • 精度よく計測するために毎回ElapsedTicksとStopwatch.Frequencyを使ってごにょごにょしないといけない。

    というわけで、最初にNewしてStartして、プログラム上のいろんな位置でLapTimeを測って、後で全部をミリ秒単位のdouble配列で取り出すサンプル。
    実行速度検証にどうぞ。

    public class k2kLapStopwatch : Stopwatch
    {
        private long[] LapTicks;
        private int LapCounter;
     
        public k2kLapStopwatch(int MaxLapCount)
        {
            LapTicks = new long[MaxLapCount];
            LapCounter = 0;
        }
     
        public void StoreLap()
        {
            LapTicks[LapCounter] = base.ElapsedTicks;
            LapCounter++;
        }
     
        public double[] GetLapElapsedMilliseconds()
        {
            int i;
            double[] ret = new double[LapCounter];
     
            ret[0] = TicksToMilliseconds(LapTicks[0]);
            for (i = 1; i < LapCounter; i++)
            {
                ret[i] = TicksToMilliseconds(LapTicks[i] - LapTicks[i - 1]);
            }
            return ret;
        }
        private double TicksToMilliseconds(long Ticks)
        {
            return (double)Ticks / Stopwatch.Frequency * 1000.0;
        }
    }

    目的上速さが重要なので、System.Collections.Generic.Listとかは使わないし、オーバーフローしても無視ってことで。
    早速↑が正しいか検証するために、List、容量指定のList、ふつうの配列の速度測ってみると、やっぱり配列が容量未指定のListの2倍ぐらい速い。

    細かいことだとLapTicksはpublicでもいいかも。あとStartNewをオーバーライドしたりして使おうかな。。

    private void button1_Click(object sender, EventArgs e)
    {
        const int n = 1000000;
        int i;
     
        k2kLapStopwatch sw = new k2kLapStopwatch(5);
     
        List<long> list1 = new List<long>();
        List<long> list2 = new List<long>(n);
        long[] array1 = new long[n];
     
        sw.Start();
        for (i = 0; i < n; i++) list1.Add(i);
        sw.StoreLap();
        for (i = 0; i < n; i++) list2.Add(i);
        sw.StoreLap();
        for (i = 0; i < n; i++) array1[i] = i;
        sw.StoreLap();
        for (i = 0; i < n; i++) ;
        sw.StoreLap();
     
        sw.Stop();
     
        double[] ret = sw.GetLapElapsedMilliseconds();
        Console.WriteLine(string.Join(",", Array.ConvertAll<double, string>(ret, d => d.ToString())));
    }

    作りたいソフト

    最近コード書いてません。
    微妙に欲しいものはあるんだけど。

    • 複数画像のバッチ処理 : 複数画像に同じような画像処理をすることが多い。
    • 画像Viewer : PicasaのViewerが惜しい。殆ど文句無いから作んないかもな。
    • WindowsMobile用家計簿 : PCと同期。大きなテンキーインターフェースが欲しい。
    • 回転LEDディスプレイ : なんとなく。
    1 2

    ホーム > プログラミング

    カレンダー
    « 2018 年 7月 »
    S M T W T F S
    1 2 3 4 5 6 7
    8 9 10 11 12 13 14
    15 16 17 18 19 20 21
    22 23 24 25 26 27 28
    29 30 31        
    最近の反応
    メタ情報

    ページの上部に戻る