for-loop / switch-statement的性能优化

时间:2012-06-26 07:28:36

标签: c# for-loop logic switch-statement

请帮助我确定以下哪些是更优化的代码?

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

在第一种情况下,在每次迭代中都会出现始终检查开关案例条件的开销。在第二种情况下,开销不存在。我觉得第二种情况要好得多。如果有人有任何其他解决方法,请帮我建议。

7 个答案:

答案 0 :(得分:5)

低{,}连续值的switch 疯狂快 - 这种类型的跳转具有高度优化的处理。坦率地说,在绝大多数情况下,你的要求会使没有任何区别 - doWork2(i);中的任何会淹没这个;哎呀,虚拟电话本身可能会淹没它。

如果真的,真的,真的很重要(我很难想到这里真实的情况),那么:衡量它。在任何明显的情况下,方式来衡量它将使用您的实际,完全代码 - 您无法概括微微优化。

所以:

  1. 没关系
  2. 测量
  3. 没关系

答案 1 :(得分: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);
}

(写在这里,代码可能不完全编译,只是为了给你这个想法......)

答案 2 :(得分:2)

我会向自己提出问题进行优化

  1. 首先,数量有多大?是1,2,10,10000000000?
  2. 运行代码的机器有多强大?
  3. 我应该写更少的代码吗?
  4. 有人会在我写完后阅读这段代码吗?如果是这样的话 专业是他?
  5. 我缺少什么?时间?速度?还有别的吗?
  6. 什么是way?我从哪里得到它?有什么可能性 way为1或2或3?
  7. 很明显,第一个代码段将用于切换部分,直到i达到计数但计数有多大?如果它不是一个非常大的数字那么无关紧要?如果它太大而你的运行时间非常慢那么它就没用了。但是,正如我所说,如果你想要可读性并且可以保证计数很小,为什么不使用第一个呢?它比第二个更容易阅读,而且我喜欢的代码更少。

    第二个片段,看起来很流行,但如果数量很大,则应该首选。

答案 3 :(得分: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()方法没有做任何事情。但是,它会给我们一个基准时间。

我在Windows 7 x64系统上获得RELEASE版本的结果是:

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

因此,将循环移动到switch语句会使其快速超过两次。

现在让我们通过在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.

现在将循环放入开关是SLOWER!这种违反直觉的结果是典型的这类事情,并说明了在尝试优化代码时应始终执行时序测试的原因。 (并且优化这样的代码通常是你甚至不应该做的事情,除非你有充分的理由怀疑存在瓶颈。你最好花时间清理代码。)))

我做了一些其他测试,对于稍微简单的doWork()方法,test2()方法更快。它实际上在很大程度上取决于JIT编译器可以对优化做些什么。

注意:我认为我的第二个测试代码的速度差异的原因是因为JIT编译器在内联调用doWork()时可以优化'ref'调用,当它们不在循环中时TEST1();而对于test2()则不能(出于某种原因)。

答案 4 :(得分:1)

你应该测量它以确定是否值得优化(我非常确定it's not)。我个人更喜欢第一个用于可读性和简洁性(代码少,不易出错,更多“dry”)。

这是另一种更简洁的方法:

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

所有“方式”似乎都得到了解决,否则它们不会出现在同一个switch中。因此,首先将它们捆绑在一个方法中是有意义的,它会执行switch

答案 5 :(得分:0)

第二种方法更有效;无论如何,你必须完成 for 循环。但是在第一种方法中,你不必要地重复 case 语句 count 次。

答案 6 :(得分:0)

假设您在此处遇到性能问题(因为在大多数情况下,交换机确实非常快):

如果您对switch语句感到困扰,我建议您在此处应用重构。

交换机很容易被策略模式替换(因为切换值 在for循环中没有改变,根本没有必要切换。)

真正的优化目标是循环的目标,但没有上下文 我很难说清楚可以做些什么。

以下是有关重构开关的更多信息(例如,针对策略模式) CodeProject Article on refactoring switch