次の方法で共有


C#で演算速度を早くする方法

質問

2008年11月21日金曜日 4:44

 C#初心者なので良く判らないので教えてください.
 数100MBの大きな多次元配列を使った数値演算の速度に関することなのですが,C#で普通に記述したのと,C++&MFCで記述した場合,C#の方では演算速度が数倍も遅くなることが判りました.
 多少調べたところ,アンマネージコードでDLLを作り,C#からアンマネージDLLを呼び出す方法で解決できそうなことはわかりました.
 しかし,DLL呼び出しではなく,直接コード記述できた方が簡単です.
 C#でこのような演算速度を早くする方法は無いでしょうか.

すべての返信 (9)

2008年11月21日金曜日 5:59 ✅回答済み

こんにちは!(^^)!ふ~です。

>C#でこのような演算速度を早くする方法は無いでしょうか.
コツは、自作部分を最小にする。つまり、既に用意されている演算メソッドのみを使い、パラメータを入れ、結果を取りだすのみ

演算精度を最小に、整数演算に置き換えて計算すると言った使い方に徹すると言うところでしょうか。

 

 

 

 


2008年11月21日金曜日 8:33 ✅回答済み

 SomeTwe さんからの引用
 C#初心者なので良く判らないので教えてください.
 数100MBの大きな多次元配列を使った数値演算の速度に関することなのですが,C#で普通に記述したのと,C++&MFCで記述した場合,C#の方では演算速度が数倍も遅くなることが判りました.
 多少調べたところ,アンマネージコードでDLLを作り,C#からアンマネージDLLを呼び出す方法で解決できそうなことはわかりました.
 しかし,DLL呼び出しではなく,直接コード記述できた方が簡単です.
 C#でこのような演算速度を早くする方法は無いでしょうか.

 

アンマネージコードだとCPUが直接理解できるネイティブコードに置き換えられるのに対し、マネージコードだと間にワンクッション置くので、(最適化も含め)どうしても速度的にはC++の方が動作速度は速くなります。

 

!(^^)!ふ~さんが書かれた以外で高速化する方法ですが、

 ・ループはできるだけしない。

  (Forループで回数が決まっているループは、あえてループしないで回数分同じ処理を記述する)

 ・サブルーチンの呼び出しは極力しない。

  (サブルーチンの呼び出しはコストが掛かるため。ただし、プログラムは汚くなります)

 ・ループの中身で余分な処理は行わない。(例えばループの中身で毎回newしているとか)

 ・上記にも関係しますが、「ループの中で」ボトルネックになっている部分を探し出す。

等が考えられます。

 

どういった処理を行っているか解らないので何ともいえませんが、例えば100回ループする処理があったとして、そこで0.1秒短縮できれば10秒短縮できますので、ループの中身をチューニングしてみてください。

 


2008年11月21日金曜日 18:05 ✅回答済み

もしVisual Studio 2008を使っているのなら、LINQ to Objectを使って処理する方法もあるかもしれません。

 

# 速度の検証もしていないので、話の一つとして考えてみてください。

# ただ、試してみる価値はあるかも…です。

 


2008年11月24日月曜日 11:54 ✅回答済み

.NET では仮想環境で動作するようなものではないため、C++とC#で演算速度に差がでるということは、基本的に考えられないので、演算処理全体をみて演算以外の動作・記述に差があるのであろうと思います。

 

一番あやしいのは、多次元データをどのようなデータ型として定義して、どのようにしてアクセスしているかですかね。

 

C++ のコードはメモリブロックに対する単純なインデックスシーク処理になっていて、C# 側のコードは階層化されたオブジェクトの参照を行うようになっているとか、そういったアルゴリズムに影響がでるような差があったりしませんか。

 

多次元管理されているデータを1次元管理にして、インデックスからデータの検索処理を統一し、(C++側の)実行中の境界チェックを有効にした状態で速度を比較すると、同等または C# のほうが高速な結果がえらえるのではないかと思います。

 

ただ、これは演算処理以外の部分を同じにして比較しているだけで、最初にかかれた C++ のコードから比較すると遅くなっているでしょう。逆に言えば、演算以外の部分を C++ の処理にあわせれば C# のコードでも同等の速度が出せるようになるはずです。

C# で配列の境界チェックを取り除くには、すでに他の方も書かれていますが fixed 構文を使うような方法がありますし、配列オブジェクトが階層化されているようならば、データの持ち方を修正する必要があります。

 

そういった、演算以外の処理を C++ にあわせて修正してみてください。


2008年11月25日火曜日 0:43 ✅回答済み

外池と申します。私も、C#で自分で書いても「相当に速くなる」(C++に匹敵するぐらいにできる)と信じている派です。

 

四則演算程度の簡単な操作の積み重ねのプログラムであれば、C#で書いて、コンパイルして中間言語になって、実行時に再度コンパイルされてインテルのIA32命令セットに展開されたら、C++で書いてIA32命令セットにコンパイルする場合とほとんど変わりません。

 

その上で、以下の点に注意されてはいかがでしょうか?

  • C#とC++の言語仕様の違いから最終的なIA32命令セットの「効率」が違ってきます。C++のポインターに基づく配列の操作とC#の配列の操作は、やはり、C#の配列操作が遅いです。C#ではポインターが使えますので利用すべきでしょう。
  • ふーさんの仰るように、大規模な操作で、自分で書くアルゴリズムに自信がなければクラスライブラリーで提供されている機能を利用することがお奨めです。しかし、小規模な操作でクラスライブラリーの機能を多数回呼び出すことは避けるべきです。呼び出しのオーバーヘッドがバカになりません。あと、C#の場合、自作関数のインライン展開ができるかどうか細かく制御できませんので、速くしたければ関数呼び出しは減らした方が良いです。
  • ちょっと各論の細かいことですが、Math.Floor関数は使わない方が良いです。C#の場合、整数型へのキャストは「切り捨て」なので、Floor関数よりもずっと速くなります。(これはC++でも同じかな。でも、VB.NETと比べると大きく異なる。)
  • 数百メガバイトのデータとのことで、ファイルから何回かにわけてデータを読み込むことになりますでしょうか? ファイルの読み込みも、C#(というよりも.Net Frameworkのクラスライブラリーの実装)の特性(どういう読み込み方をすれば速いか? バッファーのサイズと読み取り操作の回数のバランスなど)と、C++の標準ライブラリー(あるいはMFC)の実装の特性と違いますので、C#なりの工夫が必要かと思います。

ただ、つきつめれば、C++の方がやはり速いものを書けると思います。使うハードウェアが決められている、実行時間の制限が厳しく定められている、C#ではあともうちょっとでクリアできない、というような場合はC++しかないかな、とも思います。


2008年11月25日火曜日 7:39 ✅回答済み

C/C++ の多次元配列、たとえば

 

Code Snippet

int a[3][4];

 

とした場合は、3*4=12 の int が連続したメモリ空間に割り当てられます。なので、たいていのコンパイラは、

 

Code Snippet

for (int i = 0; i < 3; i++)

  for (int j = 0; j < 4; j++)

    a[i][j] = foo();

 

といったコードは、

 

Code Snippet

int* p = &a[0][0];

for (int i = 0; i < 12; i++)

  *p++ = foo();

 

のような形で最適化されます。(実際には、もっとトリッキーなコードが生成されます)

 一方、C#の [][] は、ジャズ配列を意味します。これだと、要素へのアクセスはいつも間接参照になってしまいます。

 

C# では、C の[][] にあたる多次元配列は [,] のように記述します。

原理的には、C#でも、Cと同じぐらいの最適化はできるはずなのですが、現実にはバウンドチェックまでしてしまうようです。

 

どうしても、ポインタを使いたいならば、次のような書き方もあります。(実際、速くなります)

Code Snippet

int[,] a = new int[3,4];

fixed(int* q = &a[0, 0])

{

  int* p = q;
  for (int i = 0; i < 12; i++)

  *p++ = foo();

}


2008年12月1日月曜日 3:52 ✅回答済み

どのような処理をしているかを、もう少し詳しく書いて頂けると、
みなさんのアドバイスを得られやすいと思います。

 

個人的には、何のデータを元に配列が作成されているのかが気になります。
配列を手動で作成しているとしたら、そこにも相当な処理時間がかかっていると思われます。

 

配列を作成せず、元のデータを使用して同様の処理が可能ならば、
処理方法自体を変える、というのも選択肢と思います。


2008年11月21日金曜日 8:23

早速の返事どうもです.
今回の演算は四則演算やカウントアップのみですので,残念ながらご提案の方法は使えません.
C#の方が圧倒的に遅い理由はやはり.NETのメモリアクセスと管理によるものと思いますが,例えばunsafeやfixedなどを使えばなんとかなりそうな気がしますが,よく判りません.
 やはり,DLLで呼び出すしかないでしょうか.


2008年12月11日木曜日 7:03

こんにちは、フォーラムオペレータ大久保です。

 

SomeTwe さん、フォーラムのご利用ありがとうございます。

たくさんのアドバイスをいただいていますが、いかがでしょうか?

一般的な話ですが、演算速度を速くするのは難しいですが、それに比べれば処理速度を上げるほうが簡単な場合が多いと思います。

そういった方向で考えてみてはどうでしょう。

 

多数のアドバイスをいただきましたので、皆様の投稿に「回答済み」チェックをつけさせていただきました。

また何かありましたら MSDN フォーラムをご利用ください!