7

次のうちどれがより最適化されたコードであるかを特定するのを手伝ってください。

for(int i=0;i<count;i++)
{
    switch(way)
    {
        case 1:
            doWork1(i);
            break;
        case 2:
            doWork2(i);
            break;
        case 3:
            doWork3(i);
            break;
    }
}

また

switch(way)
{
    case 1:
        for(int i=0;i<count;i++)
        {
            doWork1(i);
        }
        break;
    case 2:
        for(int i=0;i<count;i++)
        {
            doWork2(i);
        }
        break;
    case 3:
        for(int i=0;i<count;i++)
        {
            doWork3(i);
        }
        break;
}

最初のケースでは、反復ごとに常にスイッチケースの状態をチェックするオーバーヘッドが発生します。2番目のケースでは、オーバーヘッドはありません。2番目のケースの方がはるかに良いと思います。誰かが他の回避策を持っているなら、それを提案するのを手伝ってください。

4

7 に答える 7

6

switch低い連続した値はめちゃくちゃ速いです-このタイプのジャンプは高度に最適化された処理を持っています。率直に言って、あなたが尋ねることは、ほとんどの場合、違いもありません。一体、仮想呼び出し自体がそれを圧倒する可能性があります。doWork2(i);

それが本当に、本当に、本当に重要である場合(そして、ここで実際のシナリオを考えるのに苦労している場合)、それを測定します。それが目立つシナリオでは、それを測定する唯一の方法は、実際の正確なコードを使用することです。ピコ最適化を一般化することはできません。

それで:

  1. それは問題ではありません
  2. 測定
  3. それは問題ではありません
于 2012-06-26T07:41:55.720 に答える
2

最適化のために自分自身に質問したい

  1. まず第一に、カウントはどのくらいですか?1,2,10、10000000000ですか?
  2. コードを実行するマシンはどれくらい強力ですか?
  3. 私はより少ないコードを書くことになっていますか?
  4. 私が書いた後、誰かがこのコードを読むつもりですか?もしそうなら、彼はどのくらい専門的ですか?
  5. 何が足りないのですか?時間?スピード ?他に何かありますか?
  6. wayですか?どこから入手できますか?way1、2、3になる確率はどれくらいですか?

最初のコードスニペットがカウントに達するまでスイッチ部分に移動することは明らかですが、iカウントはどのくらいですか?それほど大きな数でなければ問題ありませんか?それが大きすぎて実行時間が非常に遅くなる場合、それは役に立たない。しかし、私が言ったように、読みやすさが必要で、カウントが少ないことを保証できる場合は、最初のものを使用してみませんか?それは2番目のものよりはるかに読みやすく、私が好きなものであるコードが少なくなっています。

2番目のスニペットは見苦しいですが、カウントが膨大な数の場合は推奨されます。

于 2012-06-26T07:49:24.183 に答える
2

あなたは次のようなことをすることができます:

Func(void, int> doWork;
switch(way) 
{ 
    case 1: 
        doWork = doWork1; 
        break; 
    case 2: 
        doWork = doWork2; 
        break; 
    case 3: 
        doWork = doWork3; 
        break; 
} 
for (int i=0;i<count;i++)  
{
     doWork(i);
}

(ここに書かれているのは、アイデアを与えるためだけに、コードが完全にコンパイルされない場合があります...)

于 2012-06-26T07:31:30.267 に答える
2

実際、ここにいくつかのコメントがあるにもかかわらず、多少速くなる可能性があります。

実際にテストしてみましょう。

using System;
using System.Diagnostics;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 1000000000;

            Stopwatch sw = Stopwatch.StartNew();

            for (int way = 1; way <= 3; ++way)
                test1(count, way);

            var elapsed1 = sw.Elapsed;
            Console.WriteLine("test1() took " + elapsed1);

            sw.Restart();

            for (int way = 1; way <= 3; ++way)
                test2(count, way);

            var elapsed2 = sw.Elapsed;
            Console.WriteLine("test2() took " + elapsed2);

            Console.WriteLine("test2() was {0:f1} times as fast.", + ((double)elapsed1.Ticks)/elapsed2.Ticks);
        }

        static void test1(int count, int way)
        {
            for (int i = 0; i < count; ++i)
            {
                switch (way)
                {
                    case 1: doWork1(); break;
                    case 2: doWork2(); break;
                    case 3: doWork3(); break;
                }
            }
        }

        static void test2(int count, int way)
        {
            switch (way)
            {
                case 1:
                    for (int i = 0; i < count; ++i)
                        doWork1();
                    break;

                case 2:
                    for (int i = 0; i < count; ++i)
                        doWork2();
                    break;

                case 3:
                    for (int i = 0; i < count; ++i)
                        doWork3();
                    break;
            }
        }

        static void doWork1()
        {
        }

        static void doWork2()
        {
        }

        static void doWork3()
        {
        }
    }
}

doWork()メソッドは何もしないので、これは非常に非現実的です。ただし、ベースラインのタイミングがわかります。

Windows7x64システムでのRELEASEビルドで得られる結果は次のとおりです。

test1() took 00:00:03.8041522
test2() took 00:00:01.7916698
test2() was 2.1 times as fast.

したがって、ループをswitchステートメントに移動すると、2倍以上高速になります。

次に、doWork()にコードを追加して、もう少し現実的にしましょう。

using System;
using System.Diagnostics;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 1000000000;

            Stopwatch sw = Stopwatch.StartNew();

            for (int way = 1; way <= 3; ++way)
                test1(count, way);

            var elapsed1 = sw.Elapsed;
            Console.WriteLine("test1() took " + elapsed1);

            sw.Restart();

            for (int way = 1; way <= 3; ++way)
                test2(count, way);

            var elapsed2 = sw.Elapsed;
            Console.WriteLine("test2() took " + elapsed2);

            Console.WriteLine("test2() was {0:f1} times as fast.", + ((double)elapsed1.Ticks)/elapsed2.Ticks);
        }

        static int test1(int count, int way)
        {
            int total1 = 0, total2 = 0, total3 = 0;

            for (int i = 0; i < count; ++i)
            {
                switch (way)
                {
                    case 1: doWork1(i, ref total1); break;
                    case 2: doWork2(i, ref total2); break;
                    case 3: doWork3(i, ref total3); break;
                }
            }

            return total1 + total2 + total3;
        }

        static int test2(int count, int way)
        {
            int total1 = 0, total2 = 0, total3 = 0;

            switch (way)
            {
                case 1:
                    for (int i = 0; i < count; ++i)
                        doWork1(i, ref total1);
                    break;

                case 2:
                    for (int i = 0; i < count; ++i)
                        doWork2(i, ref total2);
                    break;

                case 3:
                    for (int i = 0; i < count; ++i)
                        doWork3(i, ref total3);
                    break;
            }

            return total1 + total2 + total3;
        }

        static void doWork1(int n, ref int total)
        {
            total += n;
        }

        static void doWork2(int n, ref int total)
        {
            total += n;
        }

        static void doWork3(int n, ref int total)
        {
            total += n;
        }
    }
}

今、私はこれらの結果を得ます:

test1() took 00:00:03.9153776
test2() took 00:00:05.3220507
test2() was 0.7 times as fast.

これで、ループをスイッチに入れるのが遅くなります。この直感に反する結果は、これらの種類のものの典型であり、コードを最適化しようとしているときに常にタイミングテストを実行する必要がある理由を示しています。(そして、このようなコードの最適化は、ボトルネックがあると疑う正当な理由がない限り、通常は行うべきではありません。コードのクリーンアップに時間を費やしたほうがよいでしょう。;))

私は他のいくつかのテストを行いましたが、少し単純なdoWork()メソッドの場合、test2()メソッドの方が高速でした。これは、JITコンパイラが最適化で何ができるかに大きく依存します。

注:2番目のテストコードの速度が異なる理由は、test1()のようにループに入っていないときに、doWork()の呼び出しをインライン化するときに、JITコンパイラが「ref」呼び出しを最適化できるためだと思います。 ; 一方、test2()の場合は、(何らかの理由で)できません。

于 2012-06-26T08:35:01.357 に答える
1

最適化する価値があるかどうかを確認するために測定する必要があります(そうではないと確信しています)。個人的には、読みやすさと簡潔さのために最初のものを好みます(コードが少なく、エラーが発生しにくく、「乾燥」している)。

さらに簡潔な別のアプローチを次に示します。

for(int i = 0; i < count; i++)
{
    doAllWays(way, i); // let the method decide what to do next
}

すべての「ウェイ」は関連しているように見えます。そうでない場合、同じに表示されませんswitch。したがって、最初にを実行する1つのメソッドにそれらをバンドルすることは理にかなっていswitchます。

于 2012-06-26T07:47:33.190 に答える
0

2番目の方法はより効率的です。関係なく、完全なforループを完了する必要があります。ただし、最初の方法では、 caseステートメントのカウント回数を不必要に繰り返しています。

于 2012-06-26T07:34:42.540 に答える
0

ここでパフォーマンスの問題があると仮定します(ほとんどの場合、スイッチは非常に高速であるため)。

switchステートメントが気になる場合は、ここでリファクタリングを適用することをお勧めします。

スイッチはストラテジーパターンに簡単に置き換えることができます(切り替えられた値はforループで変更されないため、切り替える必要はまったくありません)。

実際の最適化のターゲットはforループのターゲットですが、コンテキストがなければ、それについて何ができるかを判断するのは困難です。

リファクタリングスイッチに関する詳細情報(ストラテジーパターンなど) CodeProjectリファクタリングスイッチに関する記事

于 2012-06-26T07:53:01.173 に答える