web-dev-qa-db-ja.com

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

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

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

if (obj is ISpecialType)

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

「is」キーワード専用のIL命令があると聞いたことがありますが、それはネイティブアセンブリに翻訳されたときに高速に実行されるという意味ではありません。 「is」と他の方法のパフォーマンスに関する洞察を誰もが共有できますか?

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

90
JubJub

isを使用すると、型を確認してからその型にキャストすると、パフォーマンスが低下する可能性があります。 isは、実際にオブジェクトをチェックしている型にキャストするため、後続のキャストは冗長です。

とにかくキャストする場合は、より良いアプローチがあります:

ISpecialType t = obj as ISpecialType;

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

私は Ian を使用しています。おそらくこれはしたくないでしょう。

ただし、ご存知のように、2つの違いはほとんどなく、10,000,000回を超える反復があります

  • 列挙チェックは700ミリ秒(約)
  • ISチェックは1000ミリ秒(約)

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

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

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ミリ秒の差が生じます。

さらなるアップデート-2012年10月25日
数年後、私はこのことに気づきました。bはどこでも使用されないため、リリースではbool b = a is MyClassBを省略することを選択できます。

このコード。 。 。

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);
}

。 。 。一貫して、約57ミリ秒で着信するisチェックと、29ミリ秒で着信する列挙比較が表示されます。

[〜#〜] nb [〜#〜]まだisチェックを好む、違いは気にするには小さすぎます

69
Binary Worrier

わかりましたので、私はこれについて誰かとおしゃべりして、これをさらにテストすることにしました。私が知る限り、asisのパフォーマンスは、型情報を格納するために独自のメンバーまたは関数をテストするのに比べて、両方とも非常に優れています。

Stopwatchを使用しましたが、これは最も信頼できるアプローチではないかもしれないとわかったので、UtcNowも試しました。後で、予測不可能な作成時間を含むUtcNowに似ているプロセッサー時間アプローチも試しました。また、仮想クラスのない基本クラスを非抽象にしようとしましたが、大きな効果はないようです。

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

X64はx86よりも高速に作成されましたが、実行速度が遅い/遅いことがわかりました。

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

UtcNow:
As:499ms
は:532ms
基本プロパティ:479ms
基本フィールド:502ms
ベースROフィールド:491ms
仮想GetEnumType():502ms
仮想ブールIsB():522ms
作成時間:285ms(この数値はUtcNowでは信頼できないようです。109msと806msも取得します。)

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

UtcNow:
As:348ms
Is:375ms
基本プロパティ:329ms
基本フィールド:286ms
ベースROフィールド:309ms
仮想GetEnumType():321ms
仮想ブールIsB():332ms
作成時間: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;
    }
}
22
Jared Thirsk

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

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

例えばObjにはISpecialTypeまたはITypeを指定できます。

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

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

16
Ian

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

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

結果は、「is」を使用すると約10倍高速になります!!!

出力:

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

Is-Comparisonの時間: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);
        }
    }
}
12
Knasterbax

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));
    }
}

ドキュメンテーション here でC#のパターンマッチングの詳細を読むことができます。

9

誰もが不思議に思っている場合のために、i5-4200U CPUを搭載したノートブック上でランタイムバージョン.NET4.6(Experimantal)のスクリプトを使用して、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

3
Gru