1

かなり奇妙です。今日まで、入れ子になった if-else の前に常に高いチャンス句を配置する必要があると考えていました。

簡単なセットアップ:

配列Zoo[]には、重みに基づいて 5 つのクラスの 10,000 個のオブジェクトが含まれます。たとえば、4,3,2,1,0 (4000 匹の猫、3000 匹の犬、2000 匹のニワトリ、1000 匹のウサギ、0 匹のフクロウを意味します) であり、シャッフルするかどうかを指定できます (正確に順番に)。

次にif-else、各配列メンバーを確認するために使用します。

結果: 時間 (ミリ秒)

  Weights         43210  01234  22222  43210  01234  22222
  Shuffle         Yes    Yes    Yes    No     No     No
  Polymorphism    101    100    107    26     26     27
  If Else         77     28     59     17     16     17
  If Else Reverse 28     77     59     16     17     16
  Switch          21     21     21     18     19     18

If-Else逆のほうが よりもはるかに優れているのを見たとき、それは私の目を引きましif-elseた。ここではif-else、Cat->Dog->Chicken->Rabbit->Owl を検査します。逆バージョンでは、それらを逆の順序で検査します。

また、シャッフルなしのバージョンで、すべての方法が大幅に改善されることを誰かが説明できますか? (キャッシュまたはメモリのヒット率が高いためだと思いますか?)

アップデート

  Weights         27 9 3 1 0   0 1 3 9 27  27 9 3 1 0  0 1 3 9 27
  Shuffle         Yes          Yes         No          No
  Polymorphism    84           82          27          27
  If Else         61           20          17          16
  If Else Reverse 20           60          16          17
  Switch          21           21          18          18

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

class Animal : AnimalAction
{
    public virtual int Bart { get; private set; }
    public int Type { get; private set; }
    public Animal(int animalType)
    {
        this.Type = animalType;
    }
}
interface AnimalAction
{
    int Bart { get; }
}

class Cat : Animal
{
    public Cat()
        : base(0)
    {
    }
    public override int Bart
    {
        get
        {
            return 0;
        }
    }
}
class Dog : Animal
{
    public Dog()
        : base(1)
    {
    }
    public override int Bart
    {
        get
        {
            return 1;
        }
    }
}
class Chicken : Animal
{
    public Chicken()
        : base(2)
    {
    }
    public override int Bart
    {
        get
        {
            return 2;
        }
    }
}
class Rabbit : Animal
{
    public Rabbit()
        : base(3)
    {
    }
    public override int Bart
    {
        get
        {
            return 3;
        }
    }
}
class Owl : Animal
{
    public Owl()
        : base(4)
    {
    }
    public override int Bart
    {
        get
        {
            return 4;
        }
    }
}

class SingleDispatch
{
    readonly Animal[] Zoo;
    int totalSession;

    SingleDispatch(int totalSession, int zooSize)
    {
        this.totalSession = totalSession;
        Zoo = new Animal[zooSize];
        int[] weights = new int[5] { 0, 1, 2, 3, 4 };
        int totalWeights = weights.Sum();
        int[] tiers = new int[4];
        int accumulated = 0;
        for (int i = 0; i < 4; i++)
        {
            accumulated += weights[i] * zooSize / totalWeights;
            tiers[i] = accumulated;
        }

        for (int i = 0; i < tiers[0]; i++)
        {
            Animal nextAnimal = new Cat();
            Zoo[i] = nextAnimal;
        }
        for (int i = tiers[0]; i < tiers[1]; i++)
        {
            Animal nextAnimal = new Dog();
            Zoo[i] = nextAnimal;
        }
        for (int i = tiers[1]; i < tiers[2]; i++)
        {
            Animal nextAnimal = new Chicken();
            Zoo[i] = nextAnimal;
        }
        for (int i = tiers[2]; i < tiers[3]; i++)
        {
            Animal nextAnimal = new Rabbit();
            Zoo[i] = nextAnimal;
        }
        for (int i = tiers[3]; i < zooSize; i++)
        {
            Animal nextAnimal = new Owl();
            Zoo[i] = nextAnimal;
        }

        Zoo.FisherYatesShuffle();
    }

    public static void Benchmark()
    {
        List<Tuple<string, double>> result = new List<Tuple<string, double>>();
        SingleDispatch myBenchmark = new SingleDispatch(1000, 10000);

        result.Add(TestContainer.RunTests(10, myBenchmark.SubClassPoly));

        result.Add(TestContainer.RunTests(10, myBenchmark.Ifelse));
        result.Add(TestContainer.RunTests(10, myBenchmark.IfelseReverse));

        result.Add(TestContainer.RunTests(10, myBenchmark.Switch));

        foreach (var item in result)
        {
            Console.WriteLine("{0,-30}{1:N0}", item.Item1, item.Item2);
        }
        Console.WriteLine();
    }

    void SubClassPoly()
    {
        long sum = 0;
        for (int i = 0; i < totalSession; i++)
        {
            foreach (var myAnimal in Zoo)
            {
                sum += myAnimal.Bart;
            }
        }
    }

    void Ifelse()
    {
        long sum = 0;
        for (int i = 0; i < totalSession; i++)
        {
            foreach (var myAnimal in Zoo)
            {
                if (myAnimal.Type == 0)
                {
                    sum += 0;
                }
                else if (myAnimal.Type == 1)
                {
                    sum += 1;
                }
                else if (myAnimal.Type == 2)
                {
                    sum += 2;
                }
                else if (myAnimal.Type == 3)
                {
                    sum += 3;
                }
                else
                {
                    sum += 4;
                }
            }
        }
    }
    void IfelseReverse()
    {
        long sum = 0;
        for (int i = 0; i < totalSession; i++)
        {
            foreach (var myAnimal in Zoo)
            {
                if (myAnimal.Type == 4)
                {
                    sum += 4;
                }
                else if (myAnimal.Type == 3)
                {
                    sum += 3;
                }
                else if (myAnimal.Type == 2)
                {
                    sum += 2;
                }
                else if (myAnimal.Type == 1)
                {
                    sum += 1;
                }
                else
                {
                    sum += 0;
                }
            }
        }
    }

    void Switch()
    {
        long sum = 0;
        for (int i = 0; i < totalSession; i++)
        {
            foreach (var myAnimal in Zoo)
            {
                switch (myAnimal.Type)
                {
                    case 0:
                        sum += 0;
                        break;
                    case 1:
                        sum += 1;
                        break;
                    case 2:
                        sum += 2;
                        break;
                    case 3:
                        sum += 3;
                        break;
                    case 4:
                        sum += 4;
                        break;
                    default:
                        break;
                }
            }
        }
    }

}
4

1 に答える 1

5

分岐予測。http://igoro.com/archive/fast-and-slow-if-statements-branch-prediction-in-modern-processors/

シャッフルされていない場合は、はるかに理解しやすくなります。次の結果が前の結果と同じになると推測する非常に単純な予測子があるとします。

例 (c=猫、d=犬、o=フクロウ)

動物: CCCCC DDDDD OOOOO

予測: *CCCC CDDDD DOOOO

正: NYYY NYYY NYYYY

ご覧のとおり、予測は動物が変化したときにのみ間違っています。したがって、各タイプの動物が 1000 匹いる場合、予測子は 99% 以上の確率で正しくなります。

しかし、予測子は実際にはそのようには機能しません。

実際に起こっていること**は、それぞれの if 分岐が true または false であると予測されていることです。あなたの例のような (40%,30%,20%,10%,0%) 分布を仮定すると:

if (Animal.Type == MostCommonType) true の半分未満 (40%) 100 のうち 40 (40+30+20+10+0) else if (animal.Type == SecondMostCommonType) //true の 50%時間、60 のうち 30 (30+20+10 + 0) else if (animal.Type == ThirdMostCommonType) // true 66% の時間 30 のうち 20 (20+10) else if (animal.Type = = FourtMostCommonType) // 100% の確率で true 10 のうち 10 (10 +0)

40%、50%、および 60% のオッズでは、予測子はあまり機能しません。適切な予測 (100%) は、最も一般的でない型と最も一般的でないコード パスに関するものだけです。

ただし、if の順序を逆にすると、次のようになります。

if (animal.Type == FifthMostCommonType) // 100% の確率で False 100 のうち 0 (40+30+20+10+0) else if (animal.Type == FourtMostCommonType) // 90% の確率で False 10 のうち 10 (40+30+20+10) else if (Animal.Type == MostCommonType) //77% の確率で False 90 のうち 20 (40+30+20+) else if (animal.Type = = SecondMostCommonType) //true 57% の確率で 30/70 (40+30) else if (animal.Type == ThirdMostCommonType) // true 100% の確率で 40/40 (40+)

ほとんどすべての比較は非常に予測可能です。

次の動物が最も一般的でない動物ではないという予測は、他のどの予測よりも正確です。

要するに、この場合の失敗した分岐予測の総コストは、より多くの分岐 (つまり if ステートメント) を実行するコストよりも高くなります。

それが少し解消されることを願っています。不明な点があれば教えてください、明確にしようとします。

**そうではありませんが、真実にかなり近いです。

編集:

新しいプロセッサの分岐予測子はかなり複雑です。詳細についてはhttp://en.wikipedia.org/wiki/Branch_predictor#Static_predictionを参照してください。

シャッフルは、類似したデータのグループを削除し、各推測または予測が正しい可能性が高いようにすることで、予測子を混乱させます。まったく新しいトランプを想像してみてください。友人が各カードを手に取り、それが赤か黒かを推測するように求めます。

この時点で、かなり優れたアルゴリズムは、最後のカードが何であるかを推測することです。ほぼ毎回正しいと思います。> 90%

ただし、デッキをシャッフルした後、このアルゴリズムでは 50% の精度しか得られません。実際、50% を大幅に上回るアルゴリズムはありません。(私の知る限り、この状況で優位に立つには、残っている赤と黒の数を数えることしかありません。)

編集:再サブクラス

これは、CPU / L1/2/etc のキャッシュ ミスが原因であると推測されます。各クラスは戻り値を定数、つまり return 0 として実装するため、戻り値は関数の一部です。以下に示すようにクラスを再実装すると、すべての呼び出しで強制的にキャッシュ ミスが発生し、同じ (悪い) パフォーマンスがシャッフルされるかどうかが分かると思います。

  class Rabbit : Animal
{
    int bartVal; // using a local int should force a call to memory for each instance of the class
    public Rabbit():base(3)
    {
    bartVal = 3;
    }
    public override int Bart
    {
        get
        {
            return bartVal;
        }
    }
}
于 2012-04-18T18:48:54.410 に答える