【问题标题】:Redundant/Better Performance Code VS Optimized/Less Performance Code冗余/性能更好的代码 VS 优化/性能较差的代码
【发布时间】:2013-09-12 17:27:56
【问题描述】:

就我而言,我使用的是 C#,但问题的概念也适用于 Java。希望答案足够通用以涵盖两种语言。否则最好将问题一分为二。

我一直在想哪一个更好。

编译器是否负责增强“第二”代码以使其性能与“第一”代码一样好?

能否同时获得“更好的性能”“优化”的代码?

冗余/更好的性能代码:

string name = GetName(); // returned string could be empty
List<string> myListOfStrings = GetListOfStrings();
if(string.IsNullOrWhiteSpace(name)
{
    foreach(string s in myListOfStrings)
        Console.WriteLine(s);
}
else
{
    foreach(string s in myListOfStrings)
        Console.WriteLine(s + " (Name is: " + name);
}

优化/性能较差的代码:

string name = GetName(); // returned string could be empty
List<string> myListOfStrings = GetListOfStrings();

foreach(string s in myListOfStrings)
    Console.WriteLine(string.IsNullOrWhiteSpace(name) ? s : s + " (Name is: " + name);

显然,'first' 代码的执行时间更短,因为它在每个循环中只执行一次条件 'string.IsNullOrWhiteSpace(name)'。而“第二”代码(更好)在每次迭代时执行条件。

请考虑较长的循环执行时间而不是较短的循环执行时间,因为我知道当它很短时,性能不会有差异。

【问题讨论】:

  • 优化发生的层次有很多。例如,您的第二个示例将受益于处理器的分支预测。追求最清晰、可读和可维护的。实际需要时担心优化。
  • 我预计 Console.WriteLine 的成本将超过串联成本...
  • 这与连接或 Console.WriteLine 无关。请查看问题的最后一部分。
  • @yazanpro:在过分担心理论之前,我会做基准测试。
  • @yazanpro 继续运行它。检查一个字符串是否为空一百万次是计算机可以非常轻松/快速完成的事情。如果您想让它成为值得关注的事情,那么让if 检查成为需要点击服务或数据库来确定的事情。那么这是你不想在一个循环中做的事情,即使它只有 5 次迭代。

标签: c# java performance compiler-construction


【解决方案1】:

编译器是否负责增强“第二”代码以使其性能与“第一”代码一样好?

不,它不能。

  1. 它不知道布尔表达式在循环的迭代之间不会改变。 有可能代码每次返回的值都不一样,所以每次迭代都强制执行检查。

  2. 布尔表达式也可能有副作用。在这种情况下它不会,但编译器无法知道这一点。为了满足规范,执行此类副作用很重要,因此需要在每次迭代中执行检查。

那么,您需要问的下一个问题是,在这种情况下,执行您提到的优化是否重要?在任何情况下,我都可以想象您显示的确切代码,可能不是。检查速度很快,几乎可以肯定不会成为瓶颈。如果存在性能问题,几乎肯定会有更大的鱼。

也就是说,只需对示例进行一些更改,就可以让它变得重要。如果布尔表达式本身计算量很大(即它是数据库调用、Web 服务调用、一些昂贵的 CPU 计算等的结果),那么它可能是重要的性能优化。另一个需要考虑的情况是如果布尔表达式有副作用会发生什么。如果是MoveNextIEnumerator 的调用怎么办?如果只执行一次很重要,因为您不希望副作用发生 N 次,那么这将成为一个非常重要的问题。

在这种情况下有几种可能的解决方案。

最简单的方法很可能只计算一次布尔表达式,然后将其存储在一个变量中:

bool someValue = ComputeComplexBooleanValue();
foreach(var item in collection)
{
    if(someValue)
        doStuff(item);
    else
        doOtherStuff(item);
}

如果你想执行布尔值 0-1 次(即避免在集合为空的情况下调用它一次),那么我们可以使用 Lazy 懒惰地计算该值,但确保它仍然只是计算最多一次:

var someValue = new Lazy<bool>(() => ComputeComplexBooleanValue());
foreach (var item in collection)
{
    if (someValue.Value)
        doStuff(item);
    else
        doOtherStuff(item);
}

【讨论】:

    【解决方案2】:

    您应该始终首先采用更易于理解和维护的方式。这意味着将重复代码减少到绝对最小值 (DRY)。此外,这种微优化对于许多系统来说并不是那么重要。另请注意,较短的代码并不总是更好。

    我想我会选择这样的:

    string name = GetName(); // returned string could be empty
    bool nameIsEmpty = string.IsNullOrWhiteSpace(name);
    
    foreach (string s in GetListOfStrings()) {       
    
        string messageAddition = "";
        if (!nameIsEmpty) {
            messageAddition = " (Name is: " + name + ")";
        }
    
        Console.WriteLine(s + messageAddition);
    
        // more code which uses the computed value.. 
        // otherwise the condition can be moved out the loop
    }
    

    我发现额外的if 语句比方法调用中的?: 运算符更易于阅读,但这可能是个人喜好。

    如果您想稍后提高性能,您应该分析您的应用程序并首先开始优化最慢的代码部分。也许您的 GetListOfStrings() 方法太慢了,以至于其他代码的性能完全无关紧要。如果您测量复制循环可以显着提高性能,您可以考虑更改它。

    【讨论】:

    • 您正在根据建议的具体示例(串联)考虑解决方案。而在一般情况下,可以在 Console.WriteLine 之后添加一个您无法事先维护的全新语句。
    • 在这种情况下,您应该在循环内移动if
    • @micha 但是布尔表达式在循环的每次迭代中都会被评估,尽管它不会在迭代之间改变。那是白费力气。这就是问题的全部前提。
    • +1 因为您清楚地承认,在您看来,可读性(在某些时候)比性能更重要。
    • @yazanpro:这是一个取舍,如果性能真的没有表现出来,那么我会专注于可读性而不是担心代码的小细节,但在某些情况下,代码是可读的,但是由于多次不必要的调用,性能很差,然后我会花时间调整代码,直到我可以平衡性能和可读性。
    猜你喜欢
    • 2022-08-18
    • 1970-01-01
    • 2015-04-26
    • 1970-01-01
    • 1970-01-01
    • 2014-09-04
    • 2021-03-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多