【问题标题】:Which one of these code samples has better performance?这些代码示例中哪一个具有更好的性能?
【发布时间】:2023-03-30 14:29:01
【问题描述】:

在回复one of my questions 时,我收到了许多回答说样式 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();
}

【问题讨论】:

    标签: c# performance


    【解决方案1】:

    如果性能完全不同,我认为性能差异可以忽略不计。无论如何,编译器可能会将它们优化为相同的形式。

    唯一的实质性区别是风格。

    我喜欢样式 1,因为循环有一个入口点(每次迭代)和一个出口点(每次迭代),因此很容易在循环末尾插入调试代码并知道它会被调用。这与函数的一个入口和出口点背后的原理相同(出于相同的原因)。话虽这么说,但过多的缩进可能难以阅读,所以 continue 也有它的位置。

    【讨论】:

    • 风格在另一个问题中被讨论过,所以这不是我在这里要问的。我其实很想知道编译器和 JIT 的输出。
    • 并涵盖了性能:差异介于完全可以忽略不计和完全不存在之间。
    • 谢谢。 :) 我只是想澄清一下,我不是在问风格,因为其他一些答案可能会谈到它。你得到了我的 +1。
    【解决方案2】:

    我必须检查一下。

    这是我的代码版本:

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

    我使用代码优化(默认发布配置)构建它,并使用反射器反汇编程序集。 结果验证这两种样式是相同的:

    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.");
        }
    }
    

    【讨论】:

    • 代码优化在这里实际上并没有做任何事情。 Debug配置下编译的结果是一样的。
    • 谢谢。我真的很感谢你的努力。但是,为了完整起见,我猜 JIT 可能与这段代码有关。你认为这会有什么不同吗?
    • 可能是这样。两种方法的 IL 代码不相同。
    【解决方案3】:

    最好的答案是查看生成的字节码/程序集并查看。然后忽略你所看到的,因为优化的 JIT 编译器无论如何都会根据对执行代码的实时分析来改变它。所以坚持使用最能表达意图的风格。

    也就是说,样式 2 应该直接跳回条件,而样式 1 可以想象,跳过 if 块只是为了再次跳转到条件。

    这一定是我见过的最好的不过早优化的例子。

    【讨论】:

    • 其实问题既不是风格也不是优化。我在上一个问题中讨论了样式,我知道在这种情况下优化是无关紧要的。但我想知道编译器/JIT 的实际输出。
    • 您可能没有明确表示过,但是 IMO 当您询问 A 是否会比 B 表现更好时,您是在询问优化问题。哪一个是两个相似替代方案的最佳执行代码?
    【解决方案4】:

    性能应该是一样的,不然谁说的肯定是……糊涂了。

    【讨论】:

      【解决方案5】:

      为什么不从 Jeff 的书中学习一下,像 this question 那样对这两段代码进行计时?

      【讨论】:

      • 我的问题更具理论性,所以时间不是我想要的。我担心如果这段代码在 C++ 中(理论上)会被编译成同一个程序集,所以我担心它是否与 C# 不同!
      • @Hosma Aly,我不明白。您想知道什么表现更好,但时机不是您想要的?
      • 我更关心 JIT 的理论输出,而不是实际的时序。时间可以告诉我哪个更快,但它不能告诉我为什么它更快。
      【解决方案6】:

      代码流看起来是一样的,字节码应该是一样的。

      免责声明:我是一名 C/C++ 程序员,我不会真正使用 C#

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-26
        • 1970-01-01
        • 1970-01-01
        • 2011-05-10
        • 1970-01-01
        相关资源
        最近更新 更多