C# for-loop/switch语句的性能优化

C# for-loop/switch语句的性能优化,c#,for-loop,logic,switch-statement,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;

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

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

对于(int i=0;i您可以执行以下操作:

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);
}
Func(void,int>doWork;
道岔(路)
{ 
案例1:
道尔克=道尔克1;
打破
案例2:
道尔克=道尔克2;
打破
案例3:
道尔克=道尔克3;
打破
} 

对于(int i=0;i第二种方法效率更高;不管怎样,您都必须完成完整的for循环。但是在第一种方法中,您不必要地重复case语句count次。

A
开关
开低,连续值非常快-这种类型的跳转具有高度优化的处理。Frankly、 在绝大多数情况下,你所问的将不会产生任何影响
doWork2(i);
中的任何内容都会淹没这一点;见鬼,虚拟呼叫本身可能会淹没它

如果它真的、真的、真的很重要(我很难在这里想到一个真实的场景),那么:测量它。在任何明显的场景中,那么测量它的唯一方法将是使用您的实际的、精确的代码-您不能概括pico优化

因此:

  • 没关系
  • 没关系

  • 你应该衡量它,看看它是否值得优化(我非常肯定)。就个人而言,我更喜欢第一个,因为它可读性和简洁性(代码越少,越不容易出错,越容易出错)

    以下是另一种更为简洁的方法:

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

    所有的“方式”似乎都是相关的,否则它们不会出现在同一个
    开关中。因此,将它们捆绑在一个方法中是有意义的,该方法首先执行
    开关
    我会问自己一些优化问题

  • 首先,计数有多大?是1,2,10,1000000000吗
  • 运行代码的机器将有多强大
  • 我应该写更少的代码吗
  • 有人会在我写完后读这段代码吗?如果是,怎么读 他是专业人士吗
  • 我缺少什么?时间?速度?其他什么
  • 什么是
    方式
    ?我从哪里得到它?概率是多少 是1、2还是3
  • 很明显,第一个代码片段将用于切换部分,直到
    i
    达到count,但count有多大?如果它不是一个非常大的数字,则无关紧要?如果它太大,并且运行时间非常慢,则它是无用的。但是,正如我所说的,如果您想要可读性并且可以保证计数很小,为什么不使用fi呢第一个?它比第二个可读性好得多,而且我喜欢的代码更少


    第二个代码段看起来很难看,但如果计数是一个巨大的数字,则最好使用它。

    假设您在这里遇到性能问题(因为在大多数情况下切换速度非常非常快):

    如果您对switch语句感到烦恼,我建议在这里应用重构

    开关可以很容易地被策略模式取代(因为开关值 在for循环中没有更改,根本不需要切换)

    真正的优化目标是循环,但没有上下文 很难说对此能做些什么

    下面是关于重构开关(例如到策略模式)的更多信息

    事实上,尽管这里有一些评论,但它可以更快一些

    让我们实际测试一下:

    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()
            {
            }
        }
    }
    
    因此,将循环移动到switch语句中会使其速度提高一倍以上

    现在,让我们通过在doWork()中添加一些代码来让它更现实一些:

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

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


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

    试试这个:为什么不试试呢?@TimSchmelter我的项目在silverlight,秒表在这里不可用。优化的目的是什么?速度、可读性等等。就我个人而言,我更喜欢第一个例子,因为它更容易阅读。优化开关语句对我来说就像是微观优化。我也觉得,如果有很多优化的话如果在第二个代码中进行更多的优化,它会更好:)@vaibhav你是指性能优化,还是减少代码中的冗余(例如,DRY原则)?我不知道如何才能更有效,至少在您提供的信息中是这样。减少第二个代码的冗余,提高可读性,我想知道冗余代码是否会影响性能或性能not@vaibhav根据MG的回答,可读性应该是这里唯一真正关心的问题…但我认为代码,就您所介绍的而言,我如果有20种不同的DoWORK方法,或者DoOrk方法是多余的,那么这是另一回事。你是否只引用了<代码>开关<代码>(IL)跳转表?如果没有,考虑<代码>方式<代码>是一个昂贵的评估属性-除了跳转表之外还有没有机会进行编译器优化?
    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()
            {
            }
        }
    }
    
    test1() took 00:00:03.8041522
    test2() took 00:00:01.7916698
    test2() was 2.1 times as fast.
    
    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.