[解決済み】"as "とnullable型によるパフォーマンスの驚き
質問
C# in Depthの第4章を改訂しているところなのですが、nullable型を扱っていて、"as"演算子の使い方についてのセクションを追加していて、これで書けるようになりました。
object o = ...;
int? x = o as int?;
if (x.HasValue)
{
... // Use x.Value in here
}
この方法は、C# 1と同じように"is"の後にキャストを使用するよりもパフォーマンスを向上させることができると思いました。
しかし、そうではないようです。このアプリは基本的にオブジェクト配列内のすべての整数を合計するものですが、配列には箱型整数のほか、多くのNULL参照や文字列参照が含まれています。ベンチマークでは、C# 1 で使用するコード、"as" 演算子を使用するコード、および LINQ ソリューションを測定しています。驚いたことに、C# 1のコードはこの場合20倍速く、LINQのコード(イテレータを含むのでもっと遅いと思っていた)でも"as"のコードに勝っています。
の.NET実装は
isinst
null可能な型に対して、本当に遅いのか?それは追加の
unbox.any
という問題が発生するのでしょうか?また、他に説明があるのでしょうか?今のところ、パフォーマンスに敏感な状況でこれを使わないようにという警告を含めなければならないような気がします...。
結果は?
キャスト 10000000 : 121
As: 10000000 : 2211
Linq: 10000000 : 2143
コード
using System;
using System.Diagnostics;
using System.Linq;
class Test
{
const int Size = 30000000;
static void Main()
{
object[] values = new object[Size];
for (int i = 0; i < Size - 2; i += 3)
{
values[i] = null;
values[i+1] = "";
values[i+2] = 1;
}
FindSumWithCast(values);
FindSumWithAs(values);
FindSumWithLinq(values);
}
static void FindSumWithCast(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = 0;
foreach (object o in values)
{
if (o is int)
{
int x = (int) o;
sum += x;
}
}
sw.Stop();
Console.WriteLine("Cast: {0} : {1}", sum,
(long) sw.ElapsedMilliseconds);
}
static void FindSumWithAs(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = 0;
foreach (object o in values)
{
int? x = o as int?;
if (x.HasValue)
{
sum += x.Value;
}
}
sw.Stop();
Console.WriteLine("As: {0} : {1}", sum,
(long) sw.ElapsedMilliseconds);
}
static void FindSumWithLinq(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int sum = values.OfType<int>().Sum();
sw.Stop();
Console.WriteLine("LINQ: {0} : {1}", sum,
(long) sw.ElapsedMilliseconds);
}
}
解決方法は?
JITコンパイラが生成できる機械語コードは、明らかに最初のケースの方がはるかに効率的です。 この場合、オブジェクトはボックス化された値と同じ型の変数にのみアンボックスできるというルールが非常に役に立ちます。 これにより、JITコンパイラは、値の変換を考慮する必要がなく、非常に効率的なコードを生成することができます。
その は 演算子のテストは簡単で、オブジェクトがヌルでなく、期待される型であるかどうかをチェックするだけで、わずかな機械語命令で済みます。 キャストも簡単で、JITコンパイラはオブジェクトの値ビットの位置を知っているので、それを直接使用します。 コピーや変換は行わず、すべての機械語はインラインで、数十の命令で済みます。 これは、ボクシングが一般的だった.NET 1.0では、本当に効率的である必要がありました。
int?にキャストするのはもっと手間がかかります。 箱型整数の値表現が
Nullable<int>
. 変換が必要で、ボックス型の列挙型の可能性があるため、コードが厄介です。 JITコンパイラは、JIT_Unbox_NullableというCLRヘルパー関数の呼び出しを生成して、この作業を実行します。 これはあらゆる値型に対応する汎用関数で、型チェックのためのコードがたくさんあります。 そして、値がコピーされます。 このコードはmscorwks.dllの中に閉じこめられているので、コストを見積もるのは難しいですが、何百ものマシンコード命令があると思われます。
Linq OfType() 拡張メソッドでは、さらに
は
演算子とキャストがあります。 しかし、これは一般的な型へのキャストです。 JIT コンパイラは、任意の値型へのキャストを実行できるヘルパー関数 JIT_Unbox() の呼び出しを生成します。 へのキャストと同じぐらい遅い理由はうまく説明できません。
Nullable<int>
というのも、本来はもっと少ない労力で済むはずだからです。 ngen.exeが原因ではないでしょうか。
関連
-
[解決済み] [Solved] アセンブリ System.Web.Extensions dll はどこにありますか?
-
[解決済み】Swashbuckle/Swagger + ASP.Net Core: "Failed to load API definition" (API定義の読み込みに失敗しました
-
[解決済み】2年前のMSDateを把握する【クローズド
-
[解決済み] C#のStringとstringの違いは何ですか?
-
[解決済み] callとapplyの違いは何ですか?
-
[解決済み] SQLiteのINSERT/per-secondのパフォーマンスを向上させる
-
[解決済み] 0.1fを0にすると、なぜ10倍もパフォーマンスが落ちるのですか?
-
[解決済み] Swift Betaのパフォーマンス:配列のソート
-
[解決済み] Nullable<T>.HasValueとNullable<T> != nullの違いは何ですか?
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】プログラム実行中に1秒待つ
-
[解決済み] 保護レベルによりアクセス不能になりました。
-
[解決済み】文字列が有効な DateTime " format dd/MM/yyyy " として認識されなかった。
-
[解決済み】Excel "外部テーブルが期待された形式ではありません。"
-
[解決済み】ソケットのアドレス(プロトコル/ネットワークアドレス/ポート)は、通常1つしか使用できない?
-
[解決済み】Sequence contains no matching element(シーケンスにマッチする要素がない
-
[解決済み】HRESULTからの例外:0x800A03ECエラー
-
[解決済み】ランダムなブーリアンを生成する最速の方法
-
[解決済み] 関数を終了するには?
-
[解決済み] CLRのキャストと'as'キーワードの使用について