C#中的if语句性能表现

4
我正在尝试确定在大量迭代循环中,每个“if”语句对我的C#应用程序性能的影响。由于没有找到相关主题,因此我创建了这个问题。
为测试,我进行了两个循环:一个没有“if”,另一个带有单个“if”语句。以下是代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace IfPerformance
{
    class Program
    {
        static void Main(string[] args)
        {
            int N = 500000000;
            Stopwatch sw = new Stopwatch();

            double a = 0, b = 0;
            bool f;

            sw.Restart();
            for (int i = 0; i < N; i++)
            {
                a += 1.1;
                f = a < N;
            }
            sw.Stop();
            Console.WriteLine("Without if: " + sw.ElapsedMilliseconds + " ms");

            a = 0;
            sw.Restart();
            for (int i = 0; i < N; i++)
            {
                if (a < N)
                    a += 1.1;
                else
                    b += 1.1;
            }
            sw.Stop();
            Console.WriteLine("With if:    " + sw.ElapsedMilliseconds + " ms");
            Console.ReadKey();
        }
    }
}

我使用“优化代码”和“启动而不调试”的构建选项运行测试,结果如下:

没有if语句:154毫秒

有if语句:742毫秒

这意味着单个“if”语句导致性能减慢了近5倍。我认为这方面的讨论会很有帮助。

此外,我注意到在大型循环中存在多个额外的“if”语句可能会使我的最终应用程序减慢25%,在我看来这是相当显著的。

具体来说,我正在对一组数据进行Monte-Carlo优化,这需要对整个数据集进行多次循环。循环包含取决于用户设置的分支。从这一点上开始,“if”就出现了。

我对性能方面的专业人士提出以下问题:

  1. 在循环中增加额外的“if”语句对执行多次迭代的时间有什么影响?
  2. 如何避免减速?

如果我走错了,请发表您的意见。


4
这些代码示例是两个不同的代码,一个是布尔赋值,另一个是条件语句,因此这不是评估性能的合适方法。 - saeed
2
不要被微小的优化所困扰。编译器可能会自动优化你的很多代码。在调试模式下,你无法可靠地进行性能测试。最好的性能测试是在评估用户期望后,使用外部工具对整个程序进行测试。 - Sico
1
我已经添加了布尔赋值来弥补第二个周期中比较操作的时间,并确定纯“if”语句的速度。 - kudral
4
所以你已经确定执行一个 if 语句的成本是1.484 纳秒... - Austin Salonen
3
在一个好的编译器中,由于代码实际上并没有执行任何操作,你的代码会立即终止。这使得你的基准测试相当糟糕。if语句的成本也很难测量,因为它在很大程度上取决于分支预测和推测执行对于这种特定代码和数据模式的表现。但通常来说,if语句是非常昂贵的,我尽量避免在高性能代码中使用它。 - CodesInChaos
显示剩余10条评论
3个回答

16

这并不重要……

正如评论所说,你首先会遇到大麻烦,因为你无法在调试模式下测试性能,即使在这种情况下,你还需要考虑许多其他因素(性能测试是一个完整的大世界,并不像通常看起来那么简单)。

现在,请注意,在两个地方,你正在做两件不同的事情。如果您想查看if语句的性能,应该基本上让它们执行相同的操作。我确信分支会影响IL代码……

最后但并非最不重要的是,正如我再次所说……这并不重要……除非您确实需要运行500万次,并且在这么多地方都有这种情况,导致您的程序因此变慢。

选择可读性而不是过分纠结于能否在if语句上节省一些微秒时间。

请随意阅读Eric Lippert的文章(他拥有“仅”250K声望并且曾经是C#编译器团队的主要开发人员:),这将为您指引正确的方向:

(谈到这个问题,我猜测垃圾回收(文章4)可能是需要考虑的内容...)

然后看看:这篇有关于这个话题的详细回答

最后但同样重要的是,看看编写更快的托管代码:了解成本。这是由微软CLR性能团队的Jan Gray撰写的。老实说,我还没有阅读过这篇文章 :)。我以后会阅读的...

它不断地进行扩展...


1
@Noctis 1) 我确实需要数百万次迭代(我在一组数据上运行一些优化过程)2) 正如我所提到的,我在发布模式下运行3) 在我的最终应用程序中,循环中存在几个“if”会导致总体减速25%,我认为这是相当大的。非常感谢您提供的好链接。 - kudral
1
@Kudral 然后发布一个关于您真实应用程序的简化版本的问题。您的基准测试并不代表任何真实工作负载。至少对于整数操作,通常可以通过使用一些位移和掩码来消除 if。我在我的加密代码中经常这样做。 - CodesInChaos
1
我也经常进行基准测试,我的偏好在某些方面与Eric的不同。例如,我经常使用挂钟(UtcNow)并忽略第一次运行的差异。这些只对运行时间短的基准测试有影响,而对于运行一分钟的基准测试则没有影响。Eric还忘了提到我遇到的一些最重要的错误,例如忘记禁用Turbo Boost或背景程序夺取了一点CPU时间。 - CodesInChaos
1
感谢你的提醒,我注意到我曾经是C#编译器开发团队的成员,但目前已不再是。 - Eric Lippert
1
@EricLippert 嘿,Eric,似乎链接已经失效了,而且 tech.pro 的链接根本不起作用(其中一个甚至会跳转到一个看起来可疑的 Google 搜索页面) - Noctis
显示剩余8条评论

3
这些代码示例是两个不同的代码,一个是布尔赋值,另一个是条件语句,因此这不是评估性能的合适方法。

2
这些基准测试基本上毫无意义。 除了额外的if语句之外,还有很多其他因素需要考虑。 您还必须考虑分支预测和缓存等因素。 这些微小的优化只会妨碍您编写良好的代码。 您将花费更多的时间来优化无用的东西,而不是实现软件中的好功能...
以这种方式考虑,如果您的代码中甚至有一个设计错误,那么任何类型的优化都无法帮助您。 例如,使用不适合的数据结构(例如在快速查找时使用列表而不是字典)。

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