< =工作慢于<

时间:2015-04-17 14:42:01

标签: c# performance

关于performance comparison of < and <=我发现了一些关于SO的问题(这个问题非常低调)我总是找到相同的答案,两者之间没有性能差异。

我编写了一个比较程序(not so working fiddle...copy to your machine to run it),我在其中用两种不同的方法创建了两个循环for (int i = 0; i <= 1000000000; i++ )for (int i = 0; i < 1000000001; i++ )

我跑了100次;花了平均经过的时间,发现<=运算符的循环比运行<的运算符慢。

我多次运行程序,<=总是花费更多时间来完成。 我的结果(im ms)是:

3018.73,2772.22

2816.87,2760.62

2859.02,2797.05

我的问题是: 如果两者都不快,为什么我会看到结果的差异?我的程序有什么问题吗?

2 个答案:

答案 0 :(得分:10)

基准测试是一门艺术。您所描述的内容在物理上是不可能的,&lt; =和&lt;运算符只生成以精确相同速度执行的不同处理器指令。我稍微修改了你的程序,运行DoIt十次并从for()循环中删除两个零,所以我不必等待永远:

x86抖动:

Less Than Equal To Method Time Elapsed: 0.5
Less Than Method Time Elapsed: 0.42
Less Than Equal To Method Time Elapsed: 0.36
Less Than Method Time Elapsed: 0.46
Less Than Equal To Method Time Elapsed: 0.4
Less Than Method Time Elapsed: 0.34
Less Than Equal To Method Time Elapsed: 0.33
Less Than Method Time Elapsed: 0.35
Less Than Equal To Method Time Elapsed: 0.35
Less Than Method Time Elapsed: 0.32
Less Than Equal To Method Time Elapsed: 0.32
Less Than Method Time Elapsed: 0.32
Less Than Equal To Method Time Elapsed: 0.34
Less Than Method Time Elapsed: 0.32
Less Than Equal To Method Time Elapsed: 0.32
Less Than Method Time Elapsed: 0.31
Less Than Equal To Method Time Elapsed: 0.34
Less Than Method Time Elapsed: 0.32
Less Than Equal To Method Time Elapsed: 0.31
Less Than Method Time Elapsed: 0.32

x64抖动:

Less Than Equal To Method Time Elapsed: 0.44
Less Than Method Time Elapsed: 0.4
Less Than Equal To Method Time Elapsed: 0.44
Less Than Method Time Elapsed: 0.45
Less Than Equal To Method Time Elapsed: 0.36
Less Than Method Time Elapsed: 0.35
Less Than Equal To Method Time Elapsed: 0.38
Less Than Method Time Elapsed: 0.34
Less Than Equal To Method Time Elapsed: 0.33
Less Than Method Time Elapsed: 0.34
Less Than Equal To Method Time Elapsed: 0.34
Less Than Method Time Elapsed: 0.32
Less Than Equal To Method Time Elapsed: 0.32
Less Than Method Time Elapsed: 0.35
Less Than Equal To Method Time Elapsed: 0.32
Less Than Method Time Elapsed: 0.42
Less Than Equal To Method Time Elapsed: 0.32
Less Than Method Time Elapsed: 0.31
Less Than Equal To Method Time Elapsed: 0.32
Less Than Method Time Elapsed: 0.35

您从中获得的唯一真实信号是第一个DoIt()的执行速度慢,在测试结果中也可见,这是抖动开销。而最重要的信号是嘈杂。两个循环的中值大致相等,标准偏差相当大。

否则,当您进行微优化时,您总是得到的那种信号,代码的执行不是很确定。从通常很容易消除的.NET运行时开销来看,您的程序并不是唯一在您的计算机上运行的程序。它必须共享处理器,只是WriteLine()调用已经有影响。由conhost.exe进程执行,与您的测试同时运行,同时您的测试代码进入下一个for()循环。在你的机器,内核代码和中断处理程序上发生的其他事情也可以轮到他们了。

并且codegen可以发挥作用,例如,您应该做的一件事就是交换两个调用。处理器本身通常非常不确定地执行代码。处理器缓存的状态以及分支预测逻辑收集了多少历史数据非常重要。

当我进行基准测试时,我认为15%或更低的差异无统计学意义。寻找低于此值的差异非常困难,您必须非常仔细地研究机器代码。愚蠢的事情,如分支目标未对齐或变量未存储在处理器寄存器中,可能会对执行时间产生很大影响。不是你可以修复的东西,抖动没有足够的旋钮来调整。

答案 1 :(得分:4)

首先,有很多很多理由看到基准测试的变化,即使它们做得正确也是如此。以下是一些想到的内容:

  • 您的计算机同时运行许多其他进程,将内容切换为上下文,等等。操作系统不断接收和处理来自各种I / O设备等的中断。所有这些都可能导致计算机暂停一段时间,使您测试的实际代码的运行时间相形见绌。
  • JIT进程可以检测函数何时运行了一定次数,并根据该信息对其应用其他优化。像循环展开之类的东西可以大大减少程序必须进行的跳转的数量,这比典型的CPU操作要贵得多。重新优化指令会在第一次发生时花费时间,然后在此之后加快速度。
  • 您的硬件正在尝试进行其他优化,例如分支预测,以确保尽可能高效地使用其管道。 (如果它猜对了,它基本上可以假装它会在i++<比较完成时等待<=,然后如果找到则丢弃结果这是错误的。)这些优化的影响取决于很多因素,并不容易预测。

其次,实际上很难做好基准测试。这是我已经使用了一段时间的基准模板。它并不完美,但它非常适合确保任何新兴模式不太可能基于执行顺序或随机机会:

/* This is a benchmarking template I use in LINQPad when I want to do a
 * quick performance test. Just give it a couple of actions to test and
 * it will give you a pretty good idea of how long they take compared
 * to one another. It's not perfect: You can expect a 3% error margin
 * under ideal circumstances. But if you're not going to improve
 * performance by more than 3%, you probably don't care anyway.*/
void Main()
{
    // Enter setup code here
    var actions = new[]
    {
        new TimedAction("control", () =>
        {
            int i = 0;
        }),
        new TimedAction("<", () =>
        {
           for (int i = 0; i < 1000001; i++)
            {}
        }),
        new TimedAction("<=", () =>
        {
           for (int i = 0; i <= 1000000; i++)
            {}
        }),
        new TimedAction(">", () =>
        {
           for (int i = 1000001; i > 0; i--)
            {}
        }),
        new TimedAction(">=", () =>
        {
           for (int i = 1000000; i >= 0; i--)
            {}
        })
    };
    const int TimesToRun = 10000; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}


#region timer helper methods
// Define other methods and classes here
public void TimeActions(int iterations, params TimedAction[] actions)
{
    Stopwatch s = new Stopwatch();
    int length = actions.Length;
    var results = new ActionResult[actions.Length];
    // Perform the actions in their initial order.
    for(int i = 0; i < length; i++)
    {
        var action = actions[i];
        var result = results[i] = new ActionResult{Message = action.Message};
        // Do a dry run to get things ramped up/cached
        result.DryRun1 = s.Time(action.Action, 10);
        result.FullRun1 = s.Time(action.Action, iterations);
    }
    // Perform the actions in reverse order.
    for(int i = length - 1; i >= 0; i--)
    {
        var action = actions[i];
        var result = results[i];
        // Do a dry run to get things ramped up/cached
        result.DryRun2 = s.Time(action.Action, 10);
        result.FullRun2 = s.Time(action.Action, iterations);
    }
    results.Dump();
}

public class ActionResult
{
    public string Message {get;set;}
    public double DryRun1 {get;set;}
    public double DryRun2 {get;set;}
    public double FullRun1 {get;set;}
    public double FullRun2 {get;set;}
}

public class TimedAction
{
    public TimedAction(string message, Action action)
    {
        Message = message;
        Action = action;
    }
    public string Message {get;private set;}
    public Action Action {get;private set;}
}

public static class StopwatchExtensions
{
    public static double Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.Elapsed.TotalMilliseconds;
    }
}
#endregion

这是我在LINQPad中运行时获得的结果:

benchmark result

所以你会注意到有一些变化,特别是在早期,但是在向后和向后运行所有东西之后,没有一个明确的模式表明一种方式比另一种更快或更慢。< / p>