我写了一些代码来测试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块将for循环包装到Fibo()中时,如下所示:

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…——它实际上跑得更快!但为什么呢?

注意:我使用发行版配置编译了这个文件,并直接运行了EXE文件(在Visual Studio之外).

编辑:Jon Skeet's excellent analysis显示try-catch以某种方式导致x86CLR在此特定情况下以更有利的方式使用CPU寄存器(我认为我们还不知道原因).我确认了Jon的发现,即x64CLR没有这个区别,而且它比x86CLR更快.我还在FIBO方法中使用了int个类型而不是long个类型进行测试,然后x86CLR的速度与x64CLR一样快.


看起来这个问题已经被Roslyn解决了.同一台机器,同一个CLR版本——当使用VS 2013编译时,问题仍然存在,但当使用VS 2015编译时,问题消失了.

推荐答案

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

由于一些我们都不清楚的原因,当抖动知道块处于try保护区域时,问题代码生成路径就被避免了.

这很奇怪.我们将跟进JITter团队,看看是否可以输入一个bug,以便他们能够修复这个问题.

此外,我们正在对C#和VB编译器的Roslyn算法进行改进,以确定何时可以将本地变量设置为"短暂的"--即,只是在堆栈上推入和弹出,而不是在激活期间分配堆栈上的特定位置.我们相信,如果我们给它更好的提示,什么时候本地人可以更早地"死",抖动将能够更好地完成寄存器分配之类的工作.

感谢您引起我们的注意,并为您的奇怪行为道歉.

.net相关问答推荐

在 .NET 7 项目上设置 Sentry 时遇到问题

在`MAUI`应用中使用Android`MediaPlayer`的`prepare`方法只在发布模式下和在物理设备上崩溃

为什么解码后的字节数组与原始字节数组不同?

IANA 到 Windows 时区映射

移位比Java中的乘法和除法更快吗? .网?

什么是表达式树,如何使用它们,为什么要使用它们?

使用字典作为数据源绑定组合框

为什么要判断这个!= null?

Automapper:使用 ReverseMap() 和 ForMember() 进行双向映射

注册 COM 互操作与使程序集 COM 可见

ReaderWriterLock 与锁{}

在 Moq Callback() 调用中设置变量值

为什么 C# 多维数组不实现 IEnumerable

如何在不丢失数据的情况下重命名 Entity Framework 5 Code First 迁移中的数据库列?

在 C# 中将字符串转换为十六进制字符串

一个接口是否应该继承另一个接口

并发字典正确用法

例外:不支持 URI 格式

为什么要使用 C# 类 System.Random 而不是 System.Security.Cryptography.RandomNumberGenerator?

有没有一种简单的方法来判断 .NET Framework 版本?