C#の「is」演算子のパフォーマンス


102

高速なパフォーマンスを必要とするプログラムがあります。その内部ループの1つで、オブジェクトのタイプをテストして、特定のインターフェイスから継承するかどうかを確認する必要があります。

これを行う1つの方法は、CLRの組み込み型チェック機能を使用することです。最もエレガントな方法はおそらく「is」キーワードです。

if (obj is ISpecialType)

別のアプローチは、事前定義された列挙値を返す独自の仮想GetType()関数を基本クラスに与えることです(私の場合、実際には私はブール値のみが必要です)。その方法は高速ですが、エレガントではありません。

「is」キーワード専用のIL命令があると聞いたことがありますが、ネイティブアセンブリに変換したときに高速に実行されるわけではありません。「is」のパフォーマンスと他のメソッドのパフォーマンスについて洞察を共有できる人はいますか?

更新: すべての情報に基づいた回答をありがとう!答えの中にはいくつかの役立つポイントが広がっているようです。Andrewが「自動的にキャストを実行する」というポイントは不可欠ですが、Binary WorrierとIanが収集したパフォーマンスデータも非常に役立ちます。回答の1つがこの情報のすべてを含むように編集されているとすばらしいでしょう。


2
それがメインCLR規則の一つを破るためところで、CLRは、あなた自身のタイプはgettype()関数を作成する可能性を与えることはありません-本当に種類を
abatishchev

1
ええと、 "真の型"ルールの意味は完全にはわかりませんが、CLRにはType GetType()関数が組み込まれていることを理解しています。私がその方法を使用する場合、それはいくつかの列挙型を返す別の名前の関数であるので、名前/シンボルの競合はありません。
JubJub 2009年

3
abatishchevは「型の安全性」を意味すると思います。GetType()は、型がそれ自体のうそをつくことを防ぎ、型の安全性を維持するために非仮想です。
Andrew Hare

2
ループ内で実行する必要がないように、型準拠をプリフェッチしてキャッシュすることを検討しましたか?すべてのパフォーマンスの質問は常に大量に+1されているようですが、これはc#に対する理解が不十分なようです。実際には遅すぎるのですか?どうやって?何を試しましたか?明らかに、回答についてのコメントはあまり与えられていません...
Gusdor

回答:


114

使用するis場合は、タイプをチェックしたら、パフォーマンスを傷つけることができる、あなたはその型にキャスト。 is実際にオブジェクトをチェックしている型にキャストするため、その後のキャストは冗長になります。

とにかくキャストする場合は、より良い方法を次に示します。

ISpecialType t = obj as ISpecialType;

if (t != null)
{
    // use t here
}

1
ありがとう。しかし、条件が満たされない場合にオブジェクトをキャストしない場合は、代わりに仮想関数を使用して型をテストした方がよいでしょうか?
JubJub 2009年

4
@JubJub:いいえ。失敗すると、as基本的にis(つまり、型チェック)と同じ操作が実行されます。唯一の違いは、nullではなくが返されることですfalse
Konrad Rudolph、

74

私はイアンと一緒です、あなたはおそらくこれをしたくないでしょう。

ただし、ご存知のように、2つの違いはほとんどなく、1千万回を超えています。

  • enumチェックは700 ミリ秒(約)で行われます
  • ISチェックは1000 ミリ秒で行われ ます(約)

私は個人的にはこの方法でこの問題を修正しませんが、組み込みのISチェックである方法を1つ選択せざるを得なかった場合、パフォーマンスの違いはコーディングのオーバーヘッドを考慮する価値がありません。

私の基本クラスと派生クラス

class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
}

class MyClassA : MyBaseClass
{
    public MyClassA()
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
    }
}
class MyClassB : MyBaseClass
{
    public MyClassB()
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
    }
}

JubJub:テストに関する詳細情報のリクエストに応じて。

コンソールアプリ(デバッグビルド)から両方のテストを実行しました。各テストは次のようになります。

static void IsTest()
{
    DateTime start = DateTime.Now;
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass a;
        if (i % 2 == 0)
            a = new MyClassA();
        else
            a = new MyClassB();
        bool b = a is MyClassB;
    }
    DateTime end = DateTime.Now;
    Console.WriteLine("Is test {0} miliseconds", (end - start).TotalMilliseconds);
}

リリースで実行すると、Ianのように60〜70 msの差が出ます。

更なる更新-2012年10月25日数年
後、私はこれについて何かに気づいたときbool b = a is MyClassB、bはどこでも使用されていないため、コンパイラはリリースで省略を選択できます。

このコード。。。

public static void IsTest()
{
    long total = 0;
    var a = new MyClassA();
    var b = new MyClassB();
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass baseRef;
        if (i % 2 == 0)
            baseRef = a;//new MyClassA();
        else
            baseRef = b;// new MyClassB();
        //bool bo = baseRef is MyClassB;
        bool bo = baseRef.ClassType == MyBaseClass.ClassTypeEnum.B;
        if (bo) total += 1;
    }
    sw.Stop();
    Console.WriteLine("Is test {0} miliseconds {1}", sw.ElapsedMilliseconds, total);
}

。。。は、is約57ミリ秒で受信されるチェックと、29ミリ秒で受信される列挙型の比較を一貫して示しています。

NB 私はまだisチェックを好む、違いは気にするには小さすぎる


35
仮定ではなく、実際にパフォーマンスをテストするための+1。
Jon Tackabury、2009年

3
非常に高価なDateTime.Nowではなく、
Stopwatch

2
私はそれを採用しますが、この場合、それが結果に影響を与えるとは思いません。ありがとう:)
Binary Worrier 2009年

11
@Binary Worrier- クラスの新しいオペレーター割り当てにより、「is」操作のパフォーマンスの違いが完全に覆い隠されます。事前に割り当てられた2つの異なるインスタンスを再利用してこれらの新しい操作を削除し、コードを再実行して結果を投稿してみませんか。

1
@mcmillab:何をしている場合でも、isオペレーターが引き起こしているパフォーマンスの低下よりも桁違いに大きなボトルネックが発生すること、そしてisオペレーターを中心とした設計とコーディングについて耳を傾けることでかなりの費用がかかることを保証しますコード品質、そして最終的には賢明なパフォーマンスを自己破ることになります。この場合、私は自分の発言を支持します。「です」演算子がされないことになるだろうないあなたの実行時のパフォーマンスに問題があります。
Binary Worrier 2017

23

わかりましたので、私はこれについて誰かとチャットしていて、これをもっとテストすることにしました。私が知る限り、タイプ情報を格納するために独自のメンバーまたは関数をテストすることと比較して、asおよびのパフォーマンスisはどちらも非常に優れています。

私はを使用しましたがStopwatch、これは最も信頼できるアプローチではない可能性があることを学習したので、も試しましたUtcNow。後で、UtcNow予測不可能な作成時間を含めるのと同様のプロセッサ時間アプローチも試しました。私はまた、基本クラスを仮想なしの非抽象化にしようとしましたが、それは大きな効果を持っているようには見えませんでした。

これを16GB RAMのQuad Q6600で実行しました。50milの繰り返しでも、数値は+/- 50ミリ秒前後で跳ね返るので、小さな違いをあまり読みません。

x64はx86よりも高速に作成されたが、実行速度が遅いことが興味深い

x64リリースモード:
ストップウォッチ:
As:561ms
Is:597ms
ベースプロパティ:539ms
ベースフィールド:555ms
ベースROフィールド:552ms
仮想GetEnumType()テスト:556ms
仮想IsB()テスト:588ms
作成時間:10416ms

UtcNow:
As:499ms
Is:532ms
Baseプロパティ:479ms
Baseフィールド:502ms
Base ROフィールド:491ms
Virtual GetEnumType():502ms
Virtual bool IsB():522ms
Create Time:285ms(この数値はUtcNowでは信頼できないようです。109msも取得します。および806ms。)

x86リリースモード:
ストップウォッチ:
As:391ms
Is:423ms
ベースプロパティ:369ms
ベースフィールド:321ms
ベースROフィールド:339ms
仮想GetEnumType()テスト:361ms
仮想IsB()テスト:365ms
作成時間:14106ms

UtcNow:
As:348ms
Is:375ms
Base property:329ms
Base field:286ms
Base RO field:309ms
Virtual GetEnumType():321ms
Virtual bool IsB():332ms
Create Time:544ms(この数値はUtcNowでは信頼できないようです。)

コードのほとんどは次のとおりです。

    static readonly int iterations = 50000000;
    void IsTest()
    {
        Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1;
        MyBaseClass[] bases = new MyBaseClass[iterations];
        bool[] results1 = new bool[iterations];

        Stopwatch createTime = new Stopwatch();
        createTime.Start();
        DateTime createStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            if (i % 2 == 0) bases[i] = new MyClassA();
            else bases[i] = new MyClassB();
        }
        DateTime createStop = DateTime.UtcNow;
        createTime.Stop();


        Stopwatch isTimer = new Stopwatch();
        isTimer.Start();
        DateTime isStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] =  bases[i] is MyClassB;
        }
        DateTime isStop = DateTime.UtcNow; 
        isTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch asTimer = new Stopwatch();
        asTimer.Start();
        DateTime asStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i] as MyClassB != null;
        }
        DateTime asStop = DateTime.UtcNow; 
        asTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch baseMemberTime = new Stopwatch();
        baseMemberTime.Start();
        DateTime baseStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassType == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseStop = DateTime.UtcNow;
        baseMemberTime.Stop();
        CheckResults(ref  results1);

        Stopwatch baseFieldTime = new Stopwatch();
        baseFieldTime.Start();
        DateTime baseFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseFieldStop = DateTime.UtcNow;
        baseFieldTime.Stop();
        CheckResults(ref  results1);


        Stopwatch baseROFieldTime = new Stopwatch();
        baseROFieldTime.Start();
        DateTime baseROFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseROFieldStop = DateTime.UtcNow;
        baseROFieldTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethTime = new Stopwatch();
        virtMethTime.Start();
        DateTime virtStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].GetClassType() == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime virtStop = DateTime.UtcNow;
        virtMethTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethBoolTime = new Stopwatch();
        virtMethBoolTime.Start();
        DateTime virtBoolStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].IsB();
        }
        DateTime virtBoolStop = DateTime.UtcNow;
        virtMethBoolTime.Stop();
        CheckResults(ref  results1);


        asdf.Text +=
        "Stopwatch: " + Environment.NewLine 
          +   "As:  " + asTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           +"Is:  " + isTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           + "Base property:  " + baseMemberTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base field:  " + baseFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base RO field:  " + baseROFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType() test:  " + virtMethTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual IsB() test:  " + virtMethBoolTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Create Time :  " + createTime.ElapsedMilliseconds + "ms" + Environment.NewLine + Environment.NewLine+"UtcNow: " + Environment.NewLine + "As:  " + (asStop - asStart).Milliseconds + "ms" + Environment.NewLine + "Is:  " + (isStop - isStart).Milliseconds + "ms" + Environment.NewLine + "Base property:  " + (baseStop - baseStart).Milliseconds + "ms" + Environment.NewLine + "Base field:  " + (baseFieldStop - baseFieldStart).Milliseconds + "ms" + Environment.NewLine + "Base RO field:  " + (baseROFieldStop - baseROFieldStart).Milliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType():  " + (virtStop - virtStart).Milliseconds + "ms" + Environment.NewLine + "Virtual bool IsB():  " + (virtBoolStop - virtBoolStart).Milliseconds + "ms" + Environment.NewLine + "Create Time :  " + (createStop-createStart).Milliseconds + "ms" + Environment.NewLine;
    }
}

abstract class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
    public ClassTypeEnum ClassTypeField;
    public readonly ClassTypeEnum ClassTypeReadonlyField;
    public abstract ClassTypeEnum GetClassType();
    public abstract bool IsB();
    protected MyBaseClass(ClassTypeEnum kind)
    {
        ClassTypeReadonlyField = kind;
    }
}

class MyClassA : MyBaseClass
{
    public override bool IsB() { return false; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.A; }
    public MyClassA() : base(MyBaseClass.ClassTypeEnum.A)
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
        ClassTypeField = MyBaseClass.ClassTypeEnum.A;            
    }
}
class MyClassB : MyBaseClass
{
    public override bool IsB() { return true; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.B; }
    public MyClassB() : base(MyBaseClass.ClassTypeEnum.B)
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
        ClassTypeField = MyBaseClass.ClassTypeEnum.B;
    }
}

45
(いくつかのボーナス5amにインスパイアされたシェイクスピア...)あるべきか、そうでないべきか:それは問題です: 'コードの中で気高い人は苦しむかどうか言語学者そしてその命令を呼び出すことによって、それらを信頼しますか?推測する:疑問に思う; もういや; そして、見極めるタイミングで、頭痛の種と、時間に縛られたコーダーが相続する千の潜在意識の不思議を終わらせます。「敬意を込めて敬意を表して閉鎖します。」死ぬために、いや、眠るために。はい、私は眠るでしょう、夢を見る可能性は、クラスの最も基本から派生するかもしれないものと同じです。
Jared Thirsk、

これから、プロパティへのアクセスはフィールドへのアクセスよりもx64の方が速いと結論付けることができます!!! これはどうして私には驚きの地獄なのですか?
Didier A.

1
「50milの繰り返しでも、数値は+/- 50ミリ秒前後で跳ね返るので、小さな違いをあまり読みすぎないようにするためです。」
Jared Thirsk、

16

アンドリューは正しいです。実際、コード分析では、これは不必要なキャストとしてVisual Studioによって報告されます。

1つのアイデア(あなたが何をしているのかわからない場合は、少し暗闇の中でのショットです)ですが、私は常にこのようなチェックを避け、代わりに別のクラスを持つことを勧められています。したがって、いくつかのチェックを行い、タイプに応じて異なるアクションを実行するのではなく、クラスにそれ自体を処理する方法を知らせます...

たとえば、ObjはISpecialTypeまたはITypeです。

どちらにもDoStuff()メソッドが定義されています。ITypeでは、ISpecialTypeが他のことを実行できるのに対して、それは単に返すか、カスタムのものを実行できます。

これにより、キャストが完全に削除され、コードがよりクリーンで保守しやすくなり、クラスは独自のタスクを実行する方法を認識します。


はい、型テストがtrueの場合に行うことは、そのインターフェイスメソッドを呼び出すだけなので、そのインターフェイスメソッドを基本クラスに移動するだけで、デフォルトでは何も実行できません。これは、型をテストする仮想関数を作成するよりもエレガントな場合があります。
JubJub 2009年

私はabatishchevのコメントの後にBinary Worrierと同様のテストを行いましたが、10,000,000回の反復では60msの差しかありませんでした。
Ian

1
わあ、助けてくれてありがとう。クラス構造を再編成することが適切であると思われない限り、今のところは型チェック演算子の使用に固執すると思います。冗長にキャストしたくないので、Andrewの提案どおりに「as」演算子を使用します。
JubJub 2009年

15

タイプ比較の2つの可能性についてパフォーマンス比較を行いました

  1. myobject.GetType()== typeof(MyClass)
  2. myobjectはMyClassです

結果は次のとおりです。「is」を使用すると、約10倍速くなります!!!

出力:

タイプ比較の時間:00:00:00.456

比較の時間:00:00:00.042

私のコード:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication3
{
    class MyClass
    {
        double foo = 1.23;
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass myobj = new MyClass();
            int n = 10000000;

            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj.GetType() == typeof(MyClass);
            }

            sw.Stop();
            Console.WriteLine("Time for Type-Comparison: " + GetElapsedString(sw));

            sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj is MyClass;
            }

            sw.Stop();
            Console.WriteLine("Time for Is-Comparison: " + GetElapsedString(sw));
        }

        public static string GetElapsedString(Stopwatch sw)
        {
            TimeSpan ts = sw.Elapsed;
            return String.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
        }
    }
}

13

Andrew Hareは、isチェックを実行したときにキャストが有効だったときにパフォーマンスが失われることを指摘しましたが、C#7.0では、後で追加のキャストを回避するために魔女パターンの一致をチェックできます。

if (obj is ISpecialType st)
{
   //st is in scope here and can be used
}

さらに、複数のタイプをチェックする必要がある場合は、C#7.0のパターンマッチングコンストラクトswitchでタイプを実行できるようになりました。

public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

C#でのパターンマッチングの詳細については、こちらのドキュメントをご覧ください


1
確かに有効なソリューションですが、このC#パターンマッチング機能は、次のような "feature-envy"コードを奨励するときに悲しくなります。確かに、派生オブジェクトだけが独自の面積の計算方法を「知っている」ので、値を返すだけのロジックのカプセル化に努めるべきです。
Dib

2
SOフレームワークの新しいバージョンには適用答えを(問題の)フィルタボタンを必要とする、プラットフォームは、この答えはC#7のための正しいものの基礎を形成など
ニック・ウエストゲート

1
@Dib OOPのイデアルは、制御できない型/クラス/インターフェースを操作しているときにウィンドウからスローされます。このアプローチは、完全に異なる型の1つ以上の値を返すことができる関数の結果を処理するときにも役立ちます(C#はまだ共用体型をサポートしていないため、ライブラリを使用できますOneOf<T...>が、それらには大きな欠点があります)。 。

4

誰かが不思議に思っている場合に備えて、i5-4200U CPUを搭載したノートブックでスクリプトランタイムバージョン.NET4.6(実験的)を使用して、Unityエンジン2017.1でテストを行いました。結果:

Average Relative To Local Call LocalCall 117.33 1.00 is 241.67 2.06 Enum 139.33 1.19 VCall 294.33 2.51 GetType 276.00 2.35

完全な記事:http : //www.ennoble-studios.com/tuts/unity-c-performance-comparison-is-vs-enum-vs-virtual-call.html


記事のリンクは死んでいます。
James Wilkins、

@Jamesリンクが復活しました。
GRU

良いもの-しかし私はあなたに反対票を投じなかった(実際私はとにかく賛成票を投じた)。ご参考までに。:)
James Wilkins、

-3

私はいつもこのようにチェックすることを避け、代わりに別のクラスを持つことを勧められてきました。したがって、いくつかのチェックを実行し、タイプに応じて異なるアクションを実行するのではなく、クラスにそれ自体を処理する方法を知らせます...

たとえば、ObjはISpecialTypeまたはITypeです。

どちらにもDoStuff()メソッドが定義されています。ITypeでは、ISpecialTypeが他のことを実行できるのに対して、それは単に返すか、カスタムのものを実行できます。

これにより、キャストが完全に削除され、コードがよりクリーンで保守しやすくなり、クラスは独自のタスクを実行する方法を認識します。


1
これは質問の答えにはなりません。とにかく、コンテキストがないため、クラスは常に自分自身を処理する方法を知っているとは限りません。一部のメソッド/関数がエラーを処理するのに十分なコンテキストを持つまで、例外がコールチェーンを上ることを許可する場合、同様のロジックを例外処理に適用します。
Vakhtang 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.