「as」およびnull許容型によるパフォーマンスの驚き


330

null許容型を扱うC#in Depthの第4章を改訂し、 "as"演算子の使用に関するセクションを追加して、次のように記述できるようにします。

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    ... // Use x.Value in here
}

私はこれが本当に素晴らしいと思い、 "is"に続けてキャストを使用することで、C#1と同等のパフォーマンスを向上させることができると思いました。 。

ただし、そうではないようです。以下にサンプルテストアプリを含めました。これは基本的にオブジェクト配列内のすべての整数を合計しますが、配列には多くのnull参照と文字列参照、およびボックス化された整数が含まれています。ベンチマークは、C#1で使用する必要があるコード、「as」演算子を使用するコード、およびLINQソリューションのキックを測定します。驚いたことに、この場合、C#1コードは20倍高速です。また、LINQコード(イテレータが関与していることを考えると、遅くなることが予想されていました)でも「as」コードよりも優れています。

isinstnull許容型の.NET実装は本当に遅いのですか?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);
    }
}

8
jittedコードを見てみませんか?VSデバッガでも表示できます。
Anton Tykhyy、2009年

2
気になるのですが、CLR 4.0でもテストしましたか?
Dirk Vollmar、2009年

1
@アントン:良い点。ある時点で実行されます(ただし、現時点ではこれはVSにはありませんが)。@divo:はい。しかし、それはベータ版ですので、そこには多くのデバッグコードがあるかもしれません。
Jon Skeet、

1
今日as、null許容型で使用できることを学びました。他の値タイプでは使用できないため、興味深い。実際、もっと意外です。
leppie

3
@Lepp値型で機能しないことは完全に理にかなっています。それについて考えasて、型にキャストしようとし、失敗した場合はnullを返します。値タイプをnullに設定することはできません
Earlz

回答:


209

明らかに、JITコンパイラーが最初のケースで生成できるマシンコードは、はるかに効率的です。実際に役立つルールの1つは、ボックス化された値と同じ型の変数に対してのみオブジェクトをボックス化解除できることです。これにより、JITコンパイラは非常に効率的なコードを生成でき、値の変換を考慮する必要がありません。

あるオペレータのテストでは、オブジェクトがnullでなく、予想されるタイプのものであり、かかるが、いくつかのマシンコード命令場合だけチェックし、簡単です。キャストも簡単で、JITコンパイラはオブジェクト内の値ビットの場所を認識しており、それらを直接使用します。コピーや変換は発生せず、すべてのマシンコードはインラインであり、約12の命令を必要とします。これは、ボクシングが一般的だったときに、.NET 1.0で本当に効率的になる必要がありました。

intにキャストしていますか?より多くの作業が必要です。ボックス化された整数の値表現は、のメモリレイアウトと互換性がありませんNullable<int>。変換が必要であり、ボックス化された列挙型の可能性があるため、コードは扱いにくいです。JITコンパイラは、JIT_Unbox_Nullableという名前のCLRヘルパー関数の呼び出しを生成して、ジョブを完了します。これは、任意の値型の汎用関数であり、型をチェックするための多数のコードがあります。そして値がコピーされます。このコードはmscorwks.dll内に閉じ込められているため、コストを見積もることは困難ですが、数百のマシンコード命令が考えられます。

Linq OfType()拡張メソッドもis演算子とキャストを使用します。ただし、これはジェネリック型へのキャストです。JITコンパイラーは、任意の値タイプへのキャストを実行できるヘルパー関数JIT_Unbox()への呼び出しを生成します。Nullable<int>必要な作業が少ないはずなので、へのキャストと同じくらい遅い理由はよくわかりません。ここでngen.exeが問題を引き起こす可能性があると思います。


16
さて、私は確信しています。継承階層をたどる可能性があるため、 "is"は潜在的に高価であると考えるのに慣れていると思いますが、値型の場合、階層の可能性はないので、単純なビットごとの比較になります。 。それでも、null許容のケースのJITコードは、JITによってかなり大きく最適化できると思います。
Jon Skeet

26

ように私には思えるisinstNULL可能なタイプには本当に遅いです。方法FindSumWithCastを変えた

if (o is int)

if (o is int?)

また、実行速度が大幅に低下します。私が見ることができるILの唯一の違いは、

isinst     [mscorlib]System.Int32

に変わります

isinst     valuetype [mscorlib]System.Nullable`1<int32>

1
それだけではありません。「キャスト」場合にはisinst次にNULLかどうかについての試験が続くとされる条件付きunbox.any。null可能の場合には、無条件があり unbox.anyます。
Jon Skeet、

はい、判明両方を isinstし、unbox.anyNULL可能タイプに遅くなります。
Dirk Vollmar、2009年

@ジョン:キャストが必要な理由については、私の答えを確認できます。(私はこれが古いことを知っていますが、このqを発見したばかりで、CLRについて知っている2cを提供する必要があると思いました)。
ヨハネスルドルフ

22

これは当初、Hans Passantの優れた回答へのコメントとして始まりましたが、長すぎたため、ここに少しだけ追加します。

まず、C#asオペレーターはisinstIL命令を発行します(オペレーターも発行しisます)。(別の興味深い命令はcastclass、ダイレクトキャストを実行し、コンパイラーがランタイムチェックを省略できないことを認識している場合に発行されます。)

これが何をするかですisinstECMA 335 Partition III、4.6):

フォーマット:isinst typeTok

typeTokは、メタデータトークン(あるtypereftypedef又はtypespec所望のクラスを示します)。

場合typeTokが null非許容値型またはジェネリックパラメータの型であることは、「箱入り」と解釈されtypeTok

場合typeTokが NULL可能タイプで、Nullable<T>それは「箱入り」と解釈されますT

最も重要なこと:

実際のタイプ(検証のタイプを追跡しない)場合objがある検証アサインから入力しtypeTok isinst成功とOBJ(AS 結果は検証としてのタイプを追跡しながら)そのまま返されるtypeTok強制(§1.6)や変換(§3.27)とは異なりisinst、オブジェクトの実際のタイプは変更せず、オブジェクトIDを保持します(パーティションIを参照)。

つまり、isinstこの場合はパフォーマンスキラーではなく、追加のunbox.anyです。ハンスがJITされたコードだけを見たので、これはハンスの答えから明確ではありませんでした。一般的には、C#コンパイラが発するunbox.anyisinst T?(しかし、あなたが行う場合には、それを省略しますisinst T、時にT参照型です)。

なぜそれを行うのですか?isinst T?明らかな効果はありませんT?。つまり、を返します。代わりに、これらのすべての指示により、"boxed T"に開梱できるがあることが確認されT?ます。実際に取得するにはT?、我々はまだ私達のVHS版する必要がある"boxed T"T?コンパイラが発する理由は、unbox.any後にisinst。考えてみれば、の「ボックス形式」T?は単なるaで"boxed T"あり、unboxの作成castclassisinst実行は一貫していないため、これは理にかなっています。

標準からのいくつかの情報でハンスの発見をバックアップします、ここにそれは行きます:

(ECMA 335パーティションIII、4.33): unbox.any

値型のボックス形式に適用すると、unbox.any命令はobj(型O)に含まれる値を抽出します。(これは、のunbox後に続くのと同じldobjです。)参照型に適用される場合、unbox.any命令はcastclasstypeTok と同じ効果を持ちます。

(ECMA 335パーティションIII、4.32): unbox

通常、unboxボックス化されたオブジェクト内に既に存在する値タイプのアドレスを単に計算します。nullを使用できる値の型のボックス化を解除する場合、このアプローチは不可能です。Nullable<T>値はTsボックス操作中にボックス化に変換されるため、多くの場合、実装では新しいNullable<T>ヒープを作成し、新しく割り当てられたオブジェクトへのアドレスを計算する必要があります。


最後に引用された文にはタイプミスがあると思います。「... ヒープ上 ...」を「実行スタック上」にすべきではありませんか?いくつかの新しいGCヒープインスタンスにボックス化解除すると、元の問題がほぼ同一の新しいものに交換されたようです。
Glenn Slayden

19

興味深いことに、(この初期のテストと同様に)dynamic桁違いに遅いことを介して、オペレーターのサポートに関するフィードバックを渡しました。非常によく似た理由であると思います。Nullable<T>

お奨め愛Nullable<T>。もう1つの楽しいのは、JITがnullnull不可の構造体を検出(および削除)したとしても、次のことを実行することですNullable<T>

using System;
using System.Diagnostics;
static class Program {
    static void Main() { 
        // JIT
        TestUnrestricted<int>(1,5);
        TestUnrestricted<string>("abc",5);
        TestUnrestricted<int?>(1,5);
        TestNullable<int>(1, 5);

        const int LOOP = 100000000;
        Console.WriteLine(TestUnrestricted<int>(1, LOOP));
        Console.WriteLine(TestUnrestricted<string>("abc", LOOP));
        Console.WriteLine(TestUnrestricted<int?>(1, LOOP));
        Console.WriteLine(TestNullable<int>(1, LOOP));

    }
    static long TestUnrestricted<T>(T x, int loop) {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
    static long TestNullable<T>(T? x, int loop) where T : struct {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
}

ヨーザー。それは本当に痛い違いです。ねえ。
Jon Skeet、

これらすべてから他の利点が得られなかった場合、元のコードこれの両方に対する警告を含めるようになりました:)
Jon Skeet

私はこれが古い質問であることを知っていますが、「JITがnullnull化できない構造体を見つける(そして削除する)」とはどういう意味ですか?それはnull実行時にデフォルト値または何かで置き換えられることを意味しますか?
Justin Morgan

2
@Justin-ジェネリックメソッドは、ジェネリックパラメーター(Tなど)の任意の数の順列で実行時に使用できます。スタックなどの要件は、args(ローカルのスタックスペースの量など)に依存するため、値の型に関連する一意の順列に対して1つのJITを取得します。ただし、参照はすべて同じサイズであるため、JITを共有します。値ごとのタイプのJITを実行しているときに、いくつかの明らかなシナリオをチェックし、不可能なnullなどの理由で到達できないコードを削除しようとします。完璧ではありません、注意してください。また、上記のAOTは無視しています。
マークグラベル

無制限のnullableテストは、2.5桁遅くなりますが、count変数を使用しない場合は、最適化が行われます。どちらの場合もConsole.Write(count.ToString()+" ");後に追加watch.Stop();すると、他のテストが1桁ほど遅くなりますが、無制限のnull許容テストは変更されません。nullが合格した場合にケースをテストすると、変更もあることに注意してください。元のコードが他のテストで実際にnullチェックとインクリメントを行っていないことを確認します。Linqpad
Mark Hurd、

12

これは、上記のFindSumWithAsAndHasの結果です。 代替テキスト

これはFindSumWithCastの結果です。 代替テキスト

調査結果:

  • を使用してas、オブジェクトがInt32のインスタンスかどうかを最初にテストします。内部で使用していますisinst Int32(これは手書きのコードに似ています:if(oがint))。また、を使用してas、無条件にオブジェクトのボックス化を解除します。そして、プロパティを呼び出すことは本当のパフォーマンスキラーです(それはまだフードの下で関数です)、IL_0027

  • キャストを使用して、オブジェクトがであるかどうかを最初にテストしint if (o is int)ます。これは内部で使用していisinst Int32ます。それがintのインスタンスである場合、値IL_002Dを安全に開梱できます。

簡単に言えば、これはasアプローチを使用する疑似コードです:

int? x;

(x.HasValue, x.Value) = (o isinst Int32, o unbox Int32)

if (x.HasValue)
    sum += x.Value;    

そして、これはキャストアプローチを使用する疑似コードです:

if (o isinst Int32)
    sum += (o unbox Int32)

したがって、キャスト((int)a[i]構文はキャストのように見えますが、実際にはボックス化解除されていますが、キャストとボックス化解除は同じ構文を共有します。次回は、適切な用語を使用します)アプローチは本当に速く、値をボックス化解除するだけで済みますオブジェクトが明らかにint。同じことは、asアプローチを使用することとは言えません。


11

この回答を最新に保つために、C#7.1.NET 4.7では、このページでのほとんどの議論は今や議論の余地がないことに言及する価値があります。でこれは、最高のILコードを生成するスリムな構文をサポートします。

OPのオリジナルの例...

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    // ...use x.Value in here
}

単純になる...

if (o is int x)
{
    // ...use x in here
}

私はあなたが.NETの書いているときに、新しい構文のための1つの一般的な使用であることを発見した値の型を(すなわちstructC# )その実装IEquatable<MyStruct>(ほとんどがすべきのように)。強く型付けされたEquals(MyStruct other)メソッドを実装した後、型なしのEquals(Object obj)オーバーライド(から継承Object)を次のように正常にリダイレクトできます。

public override bool Equals(Object obj) => obj is MyStruct o && Equals(o);

 


付録:この回答で上に示した最初の2つの関数例のReleaseビルドILコードを(それぞれ)ここに示します。新しい構文のILコードは確かに1バイト小さくなっていますが、ほとんどの場合、呼び出しをゼロ(対2)にunboxし、可能な場合は操作を完全に回避することで大きく勝っています。

// static void test1(Object o, ref int y)
// {
//     int? x = o as int?;
//     if (x.HasValue)
//         y = x.Value;
// }

[0] valuetype [mscorlib]Nullable`1<int32> x
        ldarg.0
        isinst [mscorlib]Nullable`1<int32>
        unbox.any [mscorlib]Nullable`1<int32>
        stloc.0
        ldloca.s x
        call instance bool [mscorlib]Nullable`1<int32>::get_HasValue()
        brfalse.s L_001e
        ldarg.1
        ldloca.s x
        call instance !0 [mscorlib]Nullable`1<int32>::get_Value()
        stind.i4
L_001e: ret

// static void test2(Object o, ref int y)
// {
//     if (o is int x)
//         y = x;
// }

[0] int32 x,
[1] object obj2
        ldarg.0
        stloc.1
        ldloc.1
        isinst int32
        ldnull
        cgt.un
        dup
        brtrue.s L_0011
        ldc.i4.0
        br.s L_0017
L_0011: ldloc.1
        unbox.any int32
L_0017: stloc.0
        brfalse.s L_001d
        ldarg.1
        ldloc.0
        stind.i4
L_001d: ret

新しいC#7構文のパフォーマンスが以前のオプションを上回っていることを実証するさらなるテストについては、ここ(特に、例 'D')を参照してください。


9

さらにプロファイリング:

using System;
using System.Diagnostics;

class Program
{
    const int Size = 30000000;

    static void Main(string[] args)
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "";
            values[i + 2] = 1;
        }

        FindSumWithIsThenCast(values);

        FindSumWithAsThenHasThenValue(values);
        FindSumWithAsThenHasThenCast(values);

        FindSumWithManualAs(values);
        FindSumWithAsThenManualHasThenValue(values);



        Console.ReadLine();
    }

    static void FindSumWithIsThenCast(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("Is then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenValue(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 then Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (x.HasValue)
            {
                sum += (int)o;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Has then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithManualAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            bool hasValue = o is int;
            int x = hasValue ? (int)o : 0;

            if (hasValue)
            {
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Manual As: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenManualHasThenValue(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Manual Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

}

出力:

Is then Cast: 10000000 : 303
As then Has then Value: 10000000 : 3524
As then Has then Cast: 10000000 : 3272
Manual As: 10000000 : 395
As then Manual Has then Value: 10000000 : 3282

これらの数字から何を推測できますか?

  • まず、is-then-castアプローチはアプローチよりも大幅に高速です。303対3524
  • 次に、.Valueはキャストよりもわずかに遅いです。3524対3272
  • 3番目に、.HasValueは手動のhasを使用するよりもわずかに遅くなります(つまり、isを使用)。3524対3282
  • 4番目に、simulated asreal asアプローチの間でアップル間の比較(つまり、シミュレートされたHasValueの割り当てとシミュレートされた値の変換の両方が同時に行われます)を実行するとsimulated as実際のasよりはるかに速いことがわかります。395対3524
  • 最後に、最初と4番目の結論に基づいて 実装として何か問題があります^ _ ^

8

試す時間はありませんが、次のことをお勧めします。

foreach (object o in values)
        {
            int? x = o as int?;

なので

int? x;
foreach (object o in values)
        {
            x = o as int?;

あなたは毎回新しいオブジェクトを作成していますが、それは問題を完全に説明するわけではありませんが、貢献するかもしれません。


1
いいえ、私はそれを実行しました、そしてそれはわずかに遅いです。
ヘンクホルターマン

2
変数を別の場所で宣言すると、変数がキャプチャされたときに生成されたコードにのみ影響します(その時点で変数が実際のセマンティクスに影響します)。ヒープを使用して新しいオブジェクトを作成するわけではありませんint?が、を使用してスタックに新しいインスタンスを作成していることに注意してくださいunbox.any。私はそれが問題だと思います-私の推測では手作りのILが両方のオプションをここで打つことができると思います... is / castケースを認識するようにJITが最適化され、一度だけチェックすることも可能です。
Jon Skeet、

キャストが長かったので、おそらくキャストは最適化されていると思いました。
ジェームズブラック

1
is / castは最適化の簡単なターゲットです。これは非常に厄介な一般的なイディオムです。
Anton Tykhyy、2009年

4
ローカル変数は、メソッドのスタックフレームが作成されるときにスタックに割り当てられるため、メソッドで変数を宣言しても、違いはありません。(もちろん閉鎖されている場合を
除き

8

正確な型チェック構造を試しました

typeof(int) == item.GetType()item is intバージョンと同じ速さで実行され、常に数値を返します(強調:Nullable<int>配列にを書き込んだ場合でも、を使用する必要がありますtypeof(int))。null != itemここにも追加のチェックが必要です。

しかしながら

typeof(int?) == item.GetType()(とは対照的にitem is int?)高速なままですが、常にfalseを返します。

typeof-constructは、RuntimeTypeHandleを使用するため、私の目には正確な型チェックの最も速い方法です。この場合の正確な型はnullableと一致しないので、is/asおそらくNullable型のインスタンスであることを確認するために、ここでさらに負荷をかける必要があります。

そして正直に言うと、あなたは何をis Nullable<xxx> plus HasValue買うのですか?何もない。(この場合は)常に基になる(値)型に直接移動できます。値を取得するか、「いいえ、要求していた型のインスタンスではありません」のいずれかになります。(int?)null配列に書き込んだ場合でも、型チェックはfalseを返します。


興味深い... "as" + HasValue(プラスHasValueでない、注)を使用するアイデアは、型チェックを2回でなく1回だけ実行することです。「チェックと開封」を1つのステップで実行します。それはもっと速くあるべきだと感じています...しかし、明らかにそうではありません。最後の文の意味がわかりませんが、ボックス化などのことはありませんint?- int?値をボックス化すると、ボックス化されたintまたはnull参照として終了します。
Jon Skeet、2010年

7
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);
        FindSumWithAsAndHas(values);
        FindSumWithAsAndIs(values);


        FindSumWithIsThenAs(values);
        FindSumWithIsThenConvert(values);

        FindSumWithLinq(values);



        Console.ReadLine();
    }

    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 FindSumWithAsAndHas(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 and Has: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }


    static void FindSumWithAsAndIs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As and Is: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }







    static void FindSumWithIsThenAs(object[] values)
    {
        // Apple-to-apple comparison with Cast routine above.
        // Using the similar steps in Cast routine above,
        // the AS here cannot be slower than Linq.



        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {

            if (o is int)
            {
                int? x = o as int?;
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then As: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithIsThenConvert(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {            
            if (o is int)
            {
                int x = Convert.ToInt32(o);
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then Convert: {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);
    }
}

出力:

Cast: 10000000 : 456
As and Has: 10000000 : 2103
As and Is: 10000000 : 2029
Is then As: 10000000 : 1376
Is then Convert: 10000000 : 566
LINQ: 10000000 : 1811

[編集:2010-06-19]

注:以前のテストは、VS 2009を使用して、Core i7(会社の開発マシン)を使用して、VS内で構成デバッグを行いました。

以下は、VS2010を使用して、Core 2 Duoを使用する私のマシンで実行されました

Inside VS, Configuration: Debug

Cast: 10000000 : 309
As and Has: 10000000 : 3322
As and Is: 10000000 : 3249
Is then As: 10000000 : 1926
Is then Convert: 10000000 : 410
LINQ: 10000000 : 2018




Outside VS, Configuration: Debug

Cast: 10000000 : 303
As and Has: 10000000 : 3314
As and Is: 10000000 : 3230
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 418
LINQ: 10000000 : 1944




Inside VS, Configuration: Release

Cast: 10000000 : 305
As and Has: 10000000 : 3327
As and Is: 10000000 : 3265
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1932




Outside VS, Configuration: Release

Cast: 10000000 : 301
As and Has: 10000000 : 3274
As and Is: 10000000 : 3240
Is then As: 10000000 : 1904
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1936

どのフレームワークバージョンを使用していますか?私のネットブック(.NET 4RCを使用)の結果はさらに劇的です-Asを使用したバージョンはあなたの結果よりもはるかに悪いです。たぶん彼らはそれを.NET 4 RTMのために改善しましたか?私はそれでももっと速くなるかもしれないと思っています...
ジョン・スキート2010

@Michael:最適化されていないビルドを実行していましたか、それともデバッガで実行していましたか?
Jon Skeet、2010年

@ジョン:最適化されていないビルド、デバッガの下で
マイケル・ブエン

1
@Michael:正しい-私はデバッガでのパフォーマンス結果を大部分は無関係と見なす傾向があります:)
Jon Skeet

@ジョン:デバッガーの場合、VS内を意味します。はい、以前のベンチマークはデバッガーの下で行われました。私は再びVSの内部と外部でベンチマークを行い、デバッグとしてコンパイルし、リリースとしてコンパイルしました。編集内容を確認する
Michael Buen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.