一尘不染

尝试捕获加快我的代码?

c#

我编写了一些代码来测试try-catch的影响,但是看到了一些令人惊讶的结果。

static void Main(string[] args)
{
    Thread.CurrentThread.Priority = ThreadPriority.Highest;
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;

    long start = 0, stop = 0, elapsed = 0;
    double avg = 0.0;

    long temp = Fibo(1);

    for (int i = 1; i < 100000000; i++)
    {
        start = Stopwatch.GetTimestamp();
        temp = Fibo(100);
        stop = Stopwatch.GetTimestamp();

        elapsed = stop - start;
        avg = avg + ((double)elapsed - avg) / i;
    }

    Console.WriteLine("Elapsed: " + avg);
    Console.ReadKey();
}

static long Fibo(int n)
{
    long n1 = 0, n2 = 1, fibo = 0;
    n++;

    for (int i = 1; i < n; i++)
    {
        n1 = n2;
        n2 = fibo;
        fibo = n1 + n2;
    }

    return fibo;
}

在我的计算机上,这将始终输出约0.96的值。

当我用一个try-catch块在Fibo()中包装for循环时,如下所示:

static long Fibo(int n)
{
    long n1 = 0, n2 = 1, fibo = 0;
    n++;

    try
    {
        for (int i = 1; i < n; i++)
        {
            n1 = n2;
            n2 = fibo;
            fibo = n1 + n2;
        }
    }
    catch {}

    return fibo;
}

现在它始终打印出0.69 …-实际上运行得更快!但为什么?

注意:我使用Release配置对它进行了编译,并直接运行EXE文件(在Visual Studio外部)。

编辑:乔恩·斯凯特(Jon Skeet)的 出色
分析
表明,在这种特定情况下,尝试捕获以某种方式导致x86
CLR以更有利的方式使用CPU寄存器(而且我认为我们尚未理解原因)。我确认了乔恩(Jon)的发现,即x64 CLR没有这种区别,并且它比x86
CLR更快。我还测试int了在Fibo方法内部使用类型而不是long类型,然后x86 CLR与x64 CLR一样快。


更新: 看起来这个问题已由罗斯林(Roslyn)解决。同一台机器,相同的CLR版本-使用VS 2013编译时问题仍然如上,但使用VS
2015编译时问题不再存在。


阅读 184

收藏
2020-05-19

共1个答案

一尘不染

一位专门研究堆栈使用优化的Roslyn工程师对此进行了研究,并向我报告C#编译器生成本地变量存储的方式与JIT编译器进行注册的方式之间的交互似乎存在问题。在相应的x86代码中进行调度。结果是在本地的加载和存储上生成次优代码。

由于我们所有人都不清楚的某些原因,当JITter知道该块处于try-protected区域中时,可以避免有问题的代码生成路径。

这很奇怪。我们将与JITter团队进行跟进,看看是否可以输入错误,以便他们可以解决此问题。

另外,我们正在努力改进Roslyn的C#和VB编译器算法,以确定何时可以使本地人成为“临时”人-即只是将其压入并弹出堆栈,而不是在堆栈上分配特定位置以用于激活的持续时间。我们相信JITter将能够更好地进行寄存器分配,如果我们能更好地提示何时可以使本地人“死”的话,那我们将做得更好。

感谢您引起我们的注意,并为您的怪异行为致歉。

2020-05-19