在比较两个可为空的变量时,为什么不使用短路逻辑“和”运算符?

时间:2018-10-29 20:28:45

标签: c# nullable

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

\r\n

这是其反编译的结果:

static void TestMethod(int? i1, int? i2)
{
    Console.WriteLine(i1 == i2);
}

结果或多或少是我所期望的,但是我想知道为什么使用逻辑“和”运算符(&)的非短路版本而不是短路版本(&&)。在我看来,后者会更有效-如果已知比较的一侧是错误的,则无需评估另一侧。在这里是否需要&运算符,或者这仅仅是一个实施细节,其重要性还不至于令人烦恼?

2 个答案:

答案 0 :(得分:11)

  

结果比我期望的要少,但我想知道为什么使用逻辑和运算符的非短路版本(&)而不是短路版本(&&)。在我看来,后者会更有效率-如果比较的一面已为人所知,则无需评估另一面。是否有强制使用&运算符的原因,或者仅仅是实现细节而已,并不重要?

这是一个很好的问题。

首先,我为Roslyn之前的可为空的降低代码以及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年以来我就没有去过Microsoft,并且从那时起他们可能已经改变了它。我不知道(更新:查看上面评论中的链接问题,似乎我错过了2011年最初实现的优化,而该优化已在2017年修复。)

要回答您的特定问题:&&的问题在于,当在操作员右侧进行的工作为<比测试和分支更便宜。测试和分支不仅仅是更多的说明。显然,它是一个分支,具有许多连锁效应。在处理器级别,分支需要分支预测,并且分支可能被错误预测。分支意味着更多基本块,请记住,jit优化器在运行时运行,这意味着jit优化器必须快速。可以说“此方法中的基本块太多,我将跳过一些优化”,因此也许不必要地添加更多基本块是一件坏事。

长话短说,如果右侧没有副作用,C#编译器将急切地生成“和”运算,并且编译器认为评估&比评估{{1 }}。通常,评估left & right的成本如此便宜,以至于分支的成本比仅仅执行急切的算法还要昂贵。

您可以在可空优化器之外的其他区域看到此内容;例如,如果您有

left ? right : false

然后将以right的形式生成,因为编译器知道无需保存任何昂贵的操作; bool x = X(); bool y = Y(); bool z = x && y; 已被 呼叫。

答案 1 :(得分:0)

如果您查看已编译的CIL代码(Roslyn 2.0),则实际上可以看到(IL_0016行)br.s指令,如果{ {1}}方法不相等(IL_0013)。我猜这是您的反编译器不在乎将其正确显示为Console.WriteLine()

GetValue()