.NETマーシャリング速度
-
19-08-2019 - |
質問
次のようなC ++メソッドシグネチャがあります:
static extern void ImageProcessing(
[MarshalAs(UnmanagedType.LPArray)]ushort[] inImage,
[MarshalAs(UnmanagedType.LPArray)]ushort[] outImage,
int inYSize, int inXSize);
内部および外部の両方のタイミングメソッドで関数をラップしました。内部的には、関数は0.24秒で実行されています。外部的には、関数は2.8秒で実行され、約12倍遅くなります。どうしたの?マーシャリングは私をそれほど遅くしますか?もしそうなら、どうすればそれを回避できますか?安全でないコードにアクセスして、ポインターなどを使用する必要がありますか?余分な時間のコストがどこから来るのかについて、私はちょっと混乱しています。
解決 3
残念なことに、答えはこれらの提案よりもはるかにありふれたものですが、助けにはなります。基本的に、タイミングの取り方がめちゃくちゃになりました。
使用していたタイミングコードは次のとおりです。
Ipp32s timer;
ippGetCpuFreqMhz(&timer);
Ipp64u globalStart = ippGetCpuClocks();
globalStart = ippGetCpuClocks() *2 - globalStart; //use this method to get rid of the overhead of getting clock ticks
//do some stuff
Ipp64u globalEnd = ippGetCpuClocks();
globalEnd = ippGetCpuClocks() *2 - globalEnd;
std::cout << "total runtime: " << ((Ipp64f)globalEnd - (Ipp64f)globalStart)/((Ipp64f)timer *1000000.0f) << " seconds" << std::endl;
このコードは、Intelコンパイラに固有のものであり、非常に正確な時間測定を提供するように設計されています。残念ながら、その非常に高い精度は、実行あたり約2.5秒のコストを意味します。タイミングコードを削除すると、その時間制約が削除されました。
まだランタイムの遅延があるように見えます-コードはそのタイミングコードをオンにして0.24秒を報告し、現在は約0.35秒のタイミングを報告しています。これは約50%の速度コストがあることを意味します。
これにコードを変更する:
static extern void ImageProcessing(
IntPtr inImage, //[MarshalAs(UnmanagedType.LPArray)]ushort[] inImage,
IntPtr outImage, //[MarshalAs(UnmanagedType.LPArray)]ushort[] outImage,
int inYSize, int inXSize);
そして次のように呼び出されます:
unsafe {
fixed (ushort* inImagePtr = theInputImage.DataArray){
fixed (ushort* outImagePtr = theResult){
ImageProcessing((IntPtr)inImagePtr,//theInputImage.DataArray,
(IntPtr)outImagePtr,//theResult,
ysize,
xsize);
}
}
}
実行可能時間を0.3秒に短縮します(3回の実行の平均)。私の好みにはまだ遅すぎますが、10倍の速度の改善は、確かに上司の許容範囲内です。
他のヒント
この記事。焦点はコンパクトフレームワークにありますが、一般的な原則はデスクトップにも適用されます。分析セクションからの関連する引用は次のとおりです。
マネージコールは、ネイティブメソッドを直接呼び出しません。代わりに、GCプリエンプションステータスを判断するための呼び出し(GCが保留中で、待機する必要があるかどうかを判断するための呼び出し)などのオーバーヘッドルーチンを実行する必要があるJITtedスタブメソッドを呼び出します。また、一部のマーシャリングコードがスタブにJITtedされる可能性もあります。これにはすべて時間がかかります。
編集:このブログ記事も読む価値がありますJITtedコードのパフォーマンスについて-CF固有ですが、まだ関連しています。また、コールスタックの深さとパフォーマンスへの影響についての記事もあります、これはおそらくCF固有のものです(デスクトップではテストされていません)。
2つの配列パラメーターをIntPtrに切り替えてみましたか? PInvokeは、マーシャリングシグネチャ内のすべての型がblittableである場合、絶対最速です。これは、Pinvokeがデータをやり取りするための単純な操作に陥ることを意味します。
私のチームでは、PInvokeレイヤーを管理する最もパフォーマンスの高い方法は次のとおりです
- マーシャルされているすべてのものがblittableであることを保証する
- 必要に応じてIntPtrクラスを操作して、配列などのマーシャル型を手動で価格を支払います。多くのラッパーメソッド/クラスがあるため、これは非常に簡単です。
<!> quot;と同様に、これはより高速になります<!> quot;答え、これはあなた自身のコードベースであるとプロファイルする必要があります。このソリューションにたどり着いたのは、いくつかの方法を検討してプロファイルを作成した後です。