为什么在比较两个nullables是否相等时,不使用短路逻辑'和'运算符?

jac*_*kbe 6 c# nullable

我有一个比较两个可空的int并将比较结果打印到控制台的方法:

static void TestMethod(int? i1, int? i2)
{
    Console.WriteLine(i1 == i2);
}
Run Code Online (Sandbox Code Playgroud)

这是它反编译的结果:

private static void TestMethod(int? i1, int? i2)
{
    int? nullable = i1;
    int? nullable2 = i2;
    Console.WriteLine((nullable.GetValueOrDefault() == nullable2.GetValueOrDefault()) & (nullable.HasValue == nullable2.HasValue));
}
Run Code Online (Sandbox Code Playgroud)

结果或多或少是我的预期,但我想知道为什么使用逻辑'和'运算符(&)的非短路版本而不是短路版本(&&).在我看来,后者会更有效率 - 如果已经知道比较的一方是错误的,那么就没有必要评估另一方.这里的&运算符是必需的还是这只是一个不够重要的实现细节?

Eri*_*ert 11

结果更像是我的预期,但我想知道为什么使用逻辑和运算符(&)的非短路版本而不是短路版本(&&).在我看来,后者会更有效率 - 如果已经知道比较的一方,那么就没有必要评估另一方.有没有理由要求使用&运算符或者这只是实现细节,这个细节不够重要?

这是一个很好的问题.

首先,我参与了罗斯林前可降低代码的代码生成器以及Roslyn中的原始实现.这是一些棘手的代码,有很多错误和错过优化的机会.我写了一系列关于Roslyn可以降低优化器工作方式的博客文章,从这里开始:

https://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/

如果这个主题感兴趣,这一系列的文章可能会有很大的帮助.第三部分特别密切相关,因为它讨论了一个相关问题:对于可空算术,我们是生成(x.HasValue & y.HasValue) ? new int?(x.Value + y.Value) : new int?(),使用&&或使用GetValueOrDefault或者是什么?(答案当然是我尝试了所有这些并选择了制作最快的最小代码的那个.)但是这个系列并没有考虑你的具体问题,这是关于可以为空的平等.可空的等式与普通的提升算术略有不同.

当然,我从2012年开始就没有去过微软,从那以后他们可能已经改变了.我不知道.(更新:看看上面评论中的链接问题,我似乎错过了2011年原始实施中的优化,并且这在2017年得到了修复.)

为了回答您的具体问题:问题&&是,它是更昂贵的&工作的时候对运营商的右侧正在做的是更便宜的比测试和转移.测试和分支不仅仅是更多的指令.它显然是一个分支,具有很多连锁效应.在处理器级别,分支需要分支预测,并且可以预测分支是错误的.分支意味着更多基本块,并且记住,jit优化器在运行时运行,这意味着jit优化器必须快速.允许说"这个方法中有太多基本块,我将跳过一些优化",因此可能不必要地添加更多基本块是一件坏事.

长话短说,如果右侧没有副作用,C#编译器将生成"和"操作,并且编译器认为评估left & right将比评估更快更短left ? right : false.通常,评估的成本right非常低廉,以至于分支的成本比仅仅进行急切的算术更昂贵.

你可以在可以为空的优化器之外的区域看到这个; 例如,如果你有

bool x = X();
bool y = Y();
bool z = x && y;
Run Code Online (Sandbox Code Playgroud)

然后生成,z = x & y因为编译器知道没有昂贵的操作要保存; Y()已经被调用.