とまと あんらいぷ…

エンジニアの活動記録とかつぶやきとか

GitHub
スポンサードリンク

C#からC++DLLを呼び出してマーシャリングの動きを確認した(後編)

やっと後編を書くことができます・・・
前回
C#からC++DLLを呼び出してマーシャリングの動きを確認した(前編)
C#からC++DLLを呼び出してマーシャリングの動きを確認した(中編)
の続きです。

今回はドラゴンの詠唱速度に焦点を当てて記載したいと思います。

その前に・・・
当記事で使っているソリューション一式をGitHubにアップしていますので
実行しながら確認したい方はどうぞ。(Debug構成だと落ちますが・・・)

C#より、C++で作成したDLLの呼び出し方法 及びマーシャリングの考察
あんちょこなソリューション名で申し訳ないです。
ではドラゴンさんの大量呼び出し、行ってみましょう。

ドラゴンを大量に呼び出してみる


さて、前の記事で呼び出したドラゴンさんですが

今回は10,000体呼び出してみます。

ドラゴンの呼び出しコード

swってのはクラス変数で定義しているStopwatchクラスです。

CastDragon()メソッドの引数は、コンソールに内容を出力するかどうかのフラグですが
今回は呼び出しのみのコードを抜粋しています。

このコード、3.6秒ほどかかるんですよね。
気軽にポンポン呼び出せる程早くはないのです。

Blittable 型と非 Blittable 型とは?


マーシャリングを高速化する前に
Blittable 型と非 Blittable 型について解説します。

MSDNによると・・・
コピーと固定

マーシャリングを行なう時、
マーシャラは引き渡す型によって挙動が変化します。

かいつまむと、C#とC++のやり取りを行なうときに相互変換が必要なものと
そうでないものを判断して、よきにはからってくれるのです。
もちろん後者の方がレスポンスがよいことが想像できます。

でですね。
「変換しなくてもC++に渡せるもの」を「Blittableである」と表現します。

Blittable 型と非 Blittable 型

によると、Blittableであるものは、C#の以下の型だけです。

SystemByte
SystemSByte
SystemInt16
SystemUInt16
SystemInt32
SystemUInt32
SystemInt64
SystemUInt64
SystemIntPtr
SystemUIntPtr
SystemSingle
SystemDouble

また、Blittable型の1次元配列もBlittableです。
ではクラスは?
というと、このサイトに
「オブジェクト参照は blittable ではありません。 」
と堂々と書いています。

マーシャリングの挙動ですが、Blittable型を使ったDLL呼び出しは滅法早いのですが
非Blittable型のマーシャリングの場合、途端に速度が落ちます。

では、構造体の配列は非Blittableなのか?というと
残念ながら非Blittableです。遅いです。

コピーと固定サイトに戻って見てみましょう。

・書式指定された Blittable クラス
・書式指定された Blittable でないクラス

というキーワードがあります。

このプロジェクトでは、
ドラゴンの頭を表す構造体で、サイズを指定してました。

が・・・これはあくまでもサイズ指定なので
「書式指定された Blittable クラス」とは認識されないようです。

C#上では、構造体の配列は必ずしも連続したメモリ領域に格納されている保証はありません。
対してC++では、構造体配列を宣言すると、必ず連続したメモリ領域に展開されます。

この違いにより、マーシャラによるメモリ領域を保証する変換が行われてレスポンス低下が発生します。

というわけで、
書式指定された Blittable クラスを作ってC++のDLLを呼び出してみましょう。

unsafe構造体の実装


書式指定された Blittable クラスを作るにはどうするか?
これはC#側で、連続したメモリ領域である実装をすればよいです。

fixedキーワードを使うことで、C++と同じように宣言することができます。

fixedを使うにはunsafeキーワードが必要で、
unsafeキーワードを使うためにプロジェクトのプロパティより
「アンセーフ コードの許可」にチェックが必要です。
20130301_unsafe.png

unsafeなメソッドの実装


unsafe キーワードを使った構造体と
それを呼び出すメソッド宣言部分を作成しました。

public fixed byte Weakness[7];ここですね。
fixedキーワードにより、C++の BYTE Weakness[7];と同義の宣言ができます。

さて、このunsafeな構造体を扱う
DragonUnsafe()メソッドですが、C#では普通には想定していない扱いをするわけですから
メソッド呼び出し部分も工夫が必要です。

そのため、第2引数の型がStatusUnsafe* pointerとしており、ポインタを受け取るよう定義しています。
※ポインタ=C++で使う、メモリの番地を示す型

呼び出し部分を見てみましょう。

C#側でも、第2引数にポインタを渡すコードを書く必要があります。
fixed( MagicCast.StatusUnsafe* ptr = cerberusHeads )という見慣れない部分が
ポインタを利用するコードです。
MagicCast.StatusUnsafe* ptrでポインタ変数を宣言し、cerberusHeadsを格納しています。
cerberusHeads自体は、fixedキーワードにより、連続したメモリ領域である事が保証されています。
これを以って「書式指定された Blittable クラス」として認識されます。

実行後、cerberusHeadsの内容がC++DLLにより書き換えられます。
fixedされたメンバの内容を取り出すには、
以下のようにC++で通用するコード→C#でのコードに書き換えます。


今回はC++で扱ったbyte配列をC#のbyteとして取り出しています。
アンマネージドコードと、マネージコードの変換部分は

Marshalクラス

を使います。
いろいろメンバがあるのでこれはまたの機械に・・・・

速度比較


さて、高速詠唱を行なうFastCastDragon()メソッドと
普通の詠唱のCastDragon()メソッドの速度比較です。

前回でもやりましたが1万回でおおよそ3秒の差があります。
これが10万回とかになってくると非常に大きいです。

20130311_run100000.png

C#でCOM相互運用を行う場合、このようにメモリの扱いを意識したコーディングを行えば
速度のトラブルも解消できそうですね。

さて、長くなりましたがこれで終了です。
不明点等あればコメントいただければ幸いです。

今回利用したソリューションは以下よりダウンロードできます。
必要な方はどうぞ。
MarshallingCC
▼この記事を読んだ方は、こんな記事も読んでいます。▼

スポンサードリンク

テーマ:プログラミング - ジャンル:コンピュータ

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://dalmore.blog7.fc2.com/tb.php/64-970bce28
この記事にトラックバックする(FC2ブログユーザー)

FC2Ad