【问题标题】:How some simple lines of code can affect execution time of next codes significantly?一些简单的代码行如何显着影响下一个代码的执行时间?
【发布时间】:2016-07-22 19:05:21
【问题描述】:

编辑:这两种情况的反汇编也根据给定的注释附加在工具 > 选项 > 调试 > 'Suppress JIT optimization on module load' > 取消选中(清除此选项,执行 JIT即使在调试模式下也可以优化)。可以看出,在有伪代码的情况下,生成的机器码并不好! 在快速版本中,代码直接使用 CPU 寄存器,但在其他版本中,它会在每次迭代中从内存中加载 p1.x 和 p1.y!我不知道我们怎么能控制这种行为!

当我试图测量一个代码块的执行时间以进行性能测试时,我发现了一些让我感到困惑的违规行为:如果我在实际测量循环之前添加一些(伪)代码块,则总运行时间会增加一个很大的因素(从 120 毫秒到 220 毫秒,即大约慢 1.8 倍!在我的 2 GHz PC 中)。 请注意,在实际情况下,这些伪代码是初始化或类似目的所需的一些不同代码。

注意: 我在 发布模式 下构建应用程序并通过 Start without Debug (Ctrl+F5) 运行它,以确保所有优化都应用以获得最佳结果。

我在这里展示一个简化的场景作为示例:

void test()
{
    Point p1 = new Point(1, 2);
    Point p = p1;
    //*** !!! Comment & UnComment the following 2 lines to see difference: ***
    p.Offset(p1); p.Offset(p1); p.Offset(p1); p.Offset(p1);
    p.Offset(p1); p.Offset(p1); p.Offset(p1); p.Offset(p1);

    Stopwatch timer = new Stopwatch();
    double dt = -1;

    for (int repeat = 1; repeat <= 5; repeat++)
    {
        p = p1; //here we reset the initial p

        timer.Restart();
        for (int i = 0; i < 50000000; i++)
        {
            p.Offset(p1);
        }
        timer.Stop();
        dt = timer.ElapsedMilliseconds;

        textBox1.Text += repeat + "] " + dt + ", p: " + p + "\r\n";
        Application.DoEvents(); if (this.IsDisposed) break;
    }
}

private void button1_Click(object sender, EventArgs e)
{
    test();
}

此问题使我无法比较优化所需的结果。 顺便说一句,这可能与struct types(?) 有关。

你知道是什么原因以及如何解决吗?

编辑(4 月 4 日):我检查并发现这种行为只发生在 struct 类型而不是 class 类型。正如我们所知,结构通常是堆栈而不是堆中的值类型。这可能在这里起作用,但我不知道..

另一个注意事项:我还发现如果我在项目属性>构建选项卡中取消选中“优化代码”选项,那么代码实际上运行得更快!

两个案例的截图如下:

拆解:

【问题讨论】:

  • 只是为了确定 - 您在 VS 之外使用 Release 构建?
  • 我无法重现该问题。在同一运行会话中,我的结果从 100 到 600 不等,无论 2 行是否注释。发布版本,没有调试器。
  • 注意:我在release mode 中构建应用程序,然后按Start Without Debug。如果没有这些设置,结果不会被 Visual Studio 优化,并且每次都不同!
  • @MarcinJuraszek:该帖子还经过编辑,提到它是在发布模式下构建并通过“无调试启动”命令运行的。
  • 已知更长的方法会导致代码效率降低。我相信有人可以针对这种特定情况分析 CIL,但如果您需要一般解释,请参阅 dotnetperls.com/method-size。您可以尝试将初始化代码重构为另一种方法,并检查是否可以提高性能。

标签: c# .net optimization performance-testing jit


【解决方案1】:

有趣的问题。

在这些情况下,汇编程序总是告诉你真相(不是 CIL,那将是完全相同的!)。可能是你的东西触发了一个边界条件,改变了最内层循环的汇编程序。

我将代码更改为控制台应用程序,进行必要的调整(抑制优化标志等)、释放模式、放置断点、F5 并点击 ctrl-alt-d。

注意:我注意到我还必须将测试用例增加 10 倍才能获得这些时间。时间和我预期的完全一样。

不过,f.ex 可能存在问题。寄存器分配,所以让我们检查一下。汇编程序从不撒谎。

案例 1 汇编程序。

                for (int i = 0; i < 500000000; i++)
00007FFE23C74526  xor         ecx,ecx  
                {
                    p.Offset(p1);
00007FFE23C74528  inc         r14d  
00007FFE23C7452B  add         r15d,2  
                for (int i = 0; i < 500000000; i++)
00007FFE23C7452F  inc         ecx  
00007FFE23C74531  cmp         ecx,1DCD6500h  
00007FFE23C74537  jl          00007FFE23C74528  
                }

案例 2 汇编器

                for (int i = 0; i < 500000000; i++)
00007FFE23C84526  xor         ecx,ecx  
                {
                    p.Offset(p1);
00007FFE23C84528  inc         r14d  
00007FFE23C8452B  add         r15d,2  
                for (int i = 0; i < 500000000; i++)
00007FFE23C8452F  inc         ecx  
00007FFE23C84531  cmp         ecx,1DCD6500h  
00007FFE23C84537  jl          00007FFE23C84528  
                }

结论

汇编程序的输出是完全一样的,数据也是——换句话说:内循环的性能也是完全一样的。

不过,请按照我的说明自行检查汇编程序的输出。如果您有另一个版本的 .NET JIT,它应该解释行为。


显然,您应该如何测试发出的汇编代码有点混乱。以下是正确的做法:

  • Tools -> Options -> Debugging -> Suppress JIT optimization on module load -> 取消选中(如果清除此选项,您可以调试优化的 JIT 代码,但您的调试能力可能会受到限制)
  • 在同一设置窗格中也禁用“仅我的代码”。
  • 将构建类型设置为“发布”模式。还要检查配置管理器。
  • 右键单击项目->“属性”->构建选项卡。勾选“优化代码”。
  • 可选,相同的选项卡:确保未选中“首选 32 位代码”。 (注意:32 位 JIT 和 64 位 JIT 是两个不同的东西。)

设置断点,F5,ctrl-alt-d,祈祷你的断点被命中,否则设置在别的地方再试一次。


我对一些 cmets 有点惊讶,所以让我来看看它们:

  • @Douglas 指出,较长的方法会导致代码效率较低。是的,我实际上已经向 Microsoft CoreCLR 团队提交了关于此问题的错误报告。确实如此,但需要注意的是 JIT 优化器只会在编译 非常大 方法时被禁用。在这种情况下非常大的是 60.000 行 IL 代码。可以在此处找到确切的限制:https://github.com/dotnet/coreclr/blob/01a5e9b4580cf6ea21de672f627402c30658ef22/src/jit/compiler.h#L7131。在这种情况下,这完全无关紧要。
  • @Noldorin 指出 OP 应该查看 IL 代码。我想指出的是,在这两种情况下,发出的 IL 代码完全相同。

【讨论】:

  • 您能准确解释一下“进行必要的调整(抑制优化标志等)”是什么意思吗?
  • 如果没记错的话,这只是 Visual Studio 中的一些编译器设置,您可以轻松找到它们。通常在调试器(或调试模式)中运行代码会阻止 JIT 优化——这会给你错误的结果。我还养成了禁用 32 位默认抖动的习惯(您可以在项目属性下找到它)。
  • 您是否使用我在帖子中提到的“无调试启动”来测试代码? “禁用 32 位默认抖动”是什么意思?你能提供确切的设置吗?据我所知,c# 没有很多选项来控制优化,我只是在项目属性 > 构建选项卡中设置了“配置管理器:发布”和“优化代码”复选框。
  • 我添加了反汇编代码和你提到的选项。优化的代码不同。请看更新帖
  • 这看起来像 32 位,而不是 64 位 asm。此外,请务必检查 Windows 更新。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-07
  • 1970-01-01
  • 2011-11-21
  • 2011-06-22
  • 1970-01-01
相关资源
最近更新 更多