数日前、C#/.net が遅すぎると述べ、C#/.net の一部の機能を削除するよう要求する N 件の記事が公開されました。
これらの記事に関係なく、c#/.net が C++ よりもそれほど遅くないことを誰もが証明したとしても、アプリケーション レベルのパフォーマンスは依然として非常に遅いというのが業界で認識されている鉄則のようです。 。
それでは、C#/.net のどこが遅いのでしょうか?
残念ながら、ほとんどのプログラマにとって、ほとんどの C# プログラムの速度が低下するという結論は受け入れにくいかもしれませんが、広く普及しています。
文字列操作
ほとんどすべてのプログラムには文字列操作があり、少なくとも 90% のプログラムには大文字と小文字の比較を無視する必要があります。少なくとも半分のコードに次のようなコードがあることを確認してください。
if (str1.ToUpper() == str2.ToUpper())
ToLower バージョンでは、次のような Web HttpModule があることも確認しました。
for (int i = 0; i < strs.Count; i++)
if (value.ToUpper() == strs[i].ToUpper())
//...
考えてみてください。ページがリクエストされるたびに、このようなコードを実行して、大きな領域に文字列インスタンスを作成する必要があります。さらに誇張されているのは、これが空間と時間を交換していると言う人もいるということです。 。 。
パフォーマンステスト
この方法が遅いと言われると、それを認めず、これが最善の方法だと考える人もいるかもしれないので、ここでは具体的なテストを使用して事実を証明する必要があります。
まず、パフォーマンスをテストするメソッドを準備します。
private static TResult MeasurePerformance
{
GC.Collect();
int gc0 = GC.CollectionCount(0);
int gc1 = GC.CollectionCount(1);
int gc2 = GC.CollectionCount(2);
TResult 結果 = デフォルト(TResult);
ストップウォッチ sw = ストップウォッチ.StartNew();
for (int i = 0; i < ループ; i++)
{
結果 = 関数(引数);
}
Console.WriteLine(sw.ElapsedMilliseconds.ToString() + "ms");
Console.WriteLine("GC 0:" + (GC.CollectionCount(0) - gc0).ToString());
Console.WriteLine("GC 1:" + (GC.CollectionCount(1) - gc1).ToString());
Console.WriteLine("GC 2:" + (GC.CollectionCount(2) - gc2).ToString());
結果を返します。
}
次に、ヒープ文字列を準備します。
プライベート静的 List
{
List
char[] chs = 新しいchar[3];
for (int i = 0; i < 10000; i++)
{
int j = i;
for (int k = 0; k < chs.Length; k++)
{
chs[k] = (char)('a' + j % 26);
j = j / 26;
}
strs.Add(新しい文字列(chs));
}
文字列を返します。
}
次に、ToUpper の実装を見てみましょう。
private static bool ImplementByToUpper(List
{
for (int i = 0; i < strs.Count; i++)
if (value.ToUpper() == strs[i].ToUpper())
true を返します。
false を返します。
}
最後に main メソッドを準備します。
List
ブール値の結果;
Console.WriteLine("ImplementByToUpper を使用");
result = MeasurePerformance(s => ImplementByToUpper(strs, s), "yZh", 1000);
Console.WriteLine("結果は " + result.ToString());
Console.ReadLine();
実行結果を見てみましょう。
ImplementByToUpper を使用する
2192ミリ秒
GC 0:247
GC 1:0
GC2:0
結果は真です
string.Equals を使用して比較テストを行ってみましょう。
private static bool ImplementByStringEquals(List
{
for (int i = 0; i < strs.Count; i++)
if (string.Equals(value, strs[i], StringComparison.CurrentCultureIgnoreCase))
true を返します。
false を返します。
}
実行結果を見てみましょう。
ImplementByStringEquals を使用する
1117ミリ秒
GC 0:0
GC 1:0
GC2:0
結果は真です
比較すると、ToUpper を使用すると 2 倍遅くなり、ジェネレーション 0 のガベージ オブジェクトが大量に発生します。空間を時間と交換すると主張する人々は、空間と引き換えに何を手に入れたのかを考えてみましょう。マイナスタイム?
辞書クラスの使用
文字列のシナリオを続けると、高速化のためにハッシュ テーブルやその他の同様の構造を使用することを考える人もいるかもしれません。これは良いアイデアですが、ハッシュ テーブルが常に最適な解決策であるとは限りません。テストをしてみましょう:
private static bool ImplementByHashSet(List
{
HashSet
set.Contains(value) を返します。
}
実行結果を見てみましょう。
ImplementByHashSet を使用する
5114ミリ秒
GC 0:38
GC 1:38
GC 2:38
結果は真です
なんと、ToUpperを使った場合と比べて速度が2倍以上遅く、第二世代のガベージコレクションも38回発生します(第二世代のガベージコレクションを実行すると、第一世代と第0世代のガベージコレクションが強制的に行われます)。
ただし、ハッシュ テーブルなどを使用して処理を高速化するという考えは非常に正しい考えですが、ハッシュ テーブル自体をキャッシュできることが前提となります。
private static Func
{
HashSet
セットを返します。
}
次に、main メソッドを次のように変更します。
Console.WriteLine("ImplementByHashSet2 を使用する");
結果 = MeasurePerformance(s =>
{
var f = ImplementByHashSet2(strs);
bool ret = false;
for (int i = 0; i < 1000; i++)
{
ret = f(s);
}
retを返します。
}, "yZh", 1);
Console.WriteLine("結果は " + result.ToString());
Console.ReadLine();
結果を見てみましょう:
ImplementByHashSet2 を使用する
6ミリ秒
GC 0:0
GC 1:0
GC2:0
結果は真です
パフォーマンスが劇的に向上しました。
もっと
C#/.net の速度を低下させる原因は何ですか?簡単に言うと、不必要なオブジェクトの作成、不必要な同期、ループ実行の非効率な方法 (firelong で批判されたリフレクションなどですが、MS ではループ内で Invoke を使用できません)、非効率なデータ構造とアルゴリズムの使用 (見てください)キャッシュの場合、同様の構造のハッシュ テーブルの驚くべきパフォーマンスを確認すると、違いがわかるでしょう)
C#/.net の敷居の低さは、ある程度多くのプログラマーを c#/.net に引き寄せるのに役立ちますが、c#/.net プログラム全体のコード レベルも大幅に低下するため、人々は非常に心配しています。
最後に、システムのパフォーマンスは、システムの最もパフォーマンスの高い部分によって決まるのではなく、システムの最もパフォーマンスの悪い部分によって決まることを忘れないでください。 16g のメモリ、100t のハード ドライブ、さらに最高級のグラフィックス カードを搭載していますが、386 CPU を搭載していない場合、このコンピューターのパフォーマンスは 386 のパフォーマンスとなります。同様に、いくら C#/.net が優れていても、プログラマのスキルが低ければ、当然、作成したプログラムのパフォーマンスも低くなります。