这些代码示例中哪一个性能更好?

3
在回答我的一个问题时,有很多人说样式2比样式1表现更好。我不明白为什么,因为我认为它们应该发出基本相同的机器指令(如果用C++编写)。您能否解释一下为什么样式2可能表现更好?

以下是两种样式:

样式1

while (!String.IsNullOrEmpty(msg = reader.readMsg()))
{
    RaiseMessageReceived();
    if (parseMsg)
    {
        ParsedMsg parsedMsg = parser.parseMsg(msg);
        RaiseMessageParsed();
        if (processMsg)
        {
            process(parsedMsg);
            RaiseMessageProcessed();
        }
    }
}

样式2:

while (!String.IsNullOrEmpty(msg = reader.readMsg()))
{
    RaiseMessageReceived();
    if (!parseMsg) continue;

    ParsedMsg parsedMsg = parser.parseMsg(msg);
    RaiseMessageParsed();
    if (!processMsg) continue;

    process(parsedMsg);
    RaiseMessageProcessed();
}
6个回答

10

我认为性能可能会有微不足道的差异,如果有的话。无论如何,编译器可能会将它们优化成相同的形式。

唯一实质性的区别在于风格上的不同。

我喜欢样式1,只因为循环每次迭代只有一个入口点和一个出口点,所以很容易在循环末尾插入调试代码并确保它会被调用。这与函数只有一个入口和出口点 (出于同样的原因) 的原则相同。尽管如此,太多缩进可能难以阅读,所以 "continue" 也有其存在的意义。


风格已在另一个问题中讨论,所以这不是我在这里询问的内容。我实际上想知道编译器和JIT的输出。 - Hosam Aly
而性能方面也被覆盖:差异在完全可以忽略到完全不存在之间。 - cletus
谢谢 :) 我只是想澄清一下,我不是在询问样式,因为一些其他答案可能会谈到这个问题。你得到了我的+1。 - Hosam Aly

4

我需要检查一下这个。

这是我的代码版本:

using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Tester t=new Tester();
            t.Method1(new Stack<string>(), new MsgParser(), true, true);
            t.Method2(new Stack<string>(), new MsgParser(), true, true);
        }
    }
    class Tester
    {
        public void Method1(Stack<string> strings, MsgParser parser, bool parseMsg, bool processMsg)
        {
            string msg;
            while (!String.IsNullOrEmpty(msg = strings.Pop()))
            {
                RaiseMessageReceived();
                if (parseMsg)
                {
                    ParsedMsg parsedMsg = parser.ParseMsg(msg);
                    RaiseMessageParsed();
                    if (processMsg)
                    {
                        process(parsedMsg);
                        RaiseMessageProcessed();
                    }
                }
            }
        }

        public void Method2(Stack<string> strings, MsgParser parser, bool parseMsg, bool processMsg)
        {
            string msg;
            while (!String.IsNullOrEmpty(msg = strings.Pop()))
            {
                RaiseMessageReceived();
                if (!parseMsg) continue;

                ParsedMsg parsedMsg = parser.ParseMsg(msg);
                RaiseMessageParsed();
                if (!processMsg) continue;

                process(parsedMsg);
                RaiseMessageProcessed();
            }

        }

        private void RaiseMessageProcessed()
        {
            Console.WriteLine("Done");
        }

        private void process(ParsedMsg msg)
        {
            Console.WriteLine(msg);
        }

        private void RaiseMessageParsed()
        {
            Console.WriteLine("Message parsed");
        }

        private void RaiseMessageReceived()
        {
            Console.WriteLine("Message received.");
        }
    }

    internal class ParsedMsg
    {
    }

    internal class MsgParser
    {
        public ParsedMsg ParseMsg(string msg)
        {
            return new ParsedMsg();
        }
    }
}

我使用代码优化(默认发布配置)构建它,并使用Reflector反汇编程序反汇编了程序集。

结果验证了这两种风格是相同的:

internal class Tester
{
    // Methods
    public void Method1(Stack<string> strings, MsgParser parser, bool parseMsg, bool processMsg)
    {
        string msg;
        while (!string.IsNullOrEmpty(msg = strings.Pop()))
        {
            this.RaiseMessageReceived();
            if (parseMsg)
            {
                ParsedMsg parsedMsg = parser.ParseMsg(msg);
                this.RaiseMessageParsed();
                if (processMsg)
                {
                    this.process(parsedMsg);
                    this.RaiseMessageProcessed();
                }
            }
        }
    }

    public void Method2(Stack<string> strings, MsgParser parser, bool parseMsg, bool processMsg)
    {
        string msg;
        while (!string.IsNullOrEmpty(msg = strings.Pop()))
        {
            this.RaiseMessageReceived();
            if (parseMsg)
            {
                ParsedMsg parsedMsg = parser.ParseMsg(msg);
                this.RaiseMessageParsed();
                if (processMsg)
                {
                    this.process(parsedMsg);
                    this.RaiseMessageProcessed();
                }
            }
        }
    }

    private void process(ParsedMsg msg)
    {
        Console.WriteLine(msg);
    }

    private void RaiseMessageParsed()
    {
        Console.WriteLine("Message parsed");
    }

    private void RaiseMessageProcessed()
    {
        Console.WriteLine("Done");
    }

    private void RaiseMessageReceived()
    {
        Console.WriteLine("Message received.");
    }
}

代码优化在这里实际上并没有起到任何作用。在调试配置下编译的结果是相同的。 - Øyvind Skaar
谢谢。我非常感激你的努力。但是,为了完整起见,我猜JIT可能与这段代码有关。你认为这会有什么区别吗? - Hosam Aly
可能是这样。这两种方法的IL代码不完全相同。 - Øyvind Skaar

3
最好的方法是查看生成的字节码/汇编代码,但会被优化JIT编译器根据执行代码的实时分析而改变。因此,请坚持最能表达意图的风格。 尽管如此,风格2应该直接跳回条件,而风格1可能会跳过if块,只有再次跳转到条件才能打断。这绝对是我见过的不要过早优化的最好例子。

实际上,这个问题既不涉及样式也不涉及优化。样式已经在我的前一个问题中讨论过了,我明白在这种情况下优化是无关紧要的。但我想知道编译器/JIT的实际输出。 - Hosam Aly
你可能没有明确地说,但在我看来,当你询问 A 是否比 B 执行更好时,你是在询问一个优化问题。哪个是两个类似选择中性能最佳的代码? - Lawrence Dol

0

性能应该是相同的,任何说不同的人都可能是...困惑了。


0
为什么不像Jeff那样,像this question中那样对两个代码片段进行计时呢?

我的问题更偏向于理论性质,所以时间并不是我关心的重点。我的担忧在于,如果这段代码是用C++编写的,它(理论上)会被编译成相同的汇编代码,那么我担心在使用C#时是否也会出现这种情况。 - Hosam Aly
@Hosma Aly,我不明白。您想知道哪个表现更好,但时间不是您要寻找的? - tuinstoel
我更关心JIT的理论输出,而不是实际时间。时间可以告诉我哪个更快,但它无法告诉我为什么它更快。 - Hosam Aly

0

代码流程看起来相同,字节码应该也一样。

免责声明:我是C/C++程序员,不太熟悉C#。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接