如果标题有意义,请跳到我的问题部分。如果没有,这里有一些背景信息。
我正在编写一些代码,它碰巧使用了空合并运算符。 我的代码:
_handle = handle ?? throw new ArgumentNullException (nameof(handle));
_handle
和 handle
都是 class Handle
类型(我编写的)。
我决定调查 IL。这是它输出的内容:
...
IL_0002: ldarg.1 // handle
IL_0003: dup
IL_0004: brtrue.s IL_0012 // transfer to line that stores into _handle
IL_0006: pop
IL_0007: ldstr "handle"
IL_000c: newobj instance void [mscorlib]System.ArgumentNullException::.ctor(string)
IL_0011: throw
IL_0012: stfld class Sandbox.V1.Handle Sandbox.V1.Spade::_handle
...
我在这里看到brtrue.s
(或者它可能是代码略有不同的brfalse.s
)指令(IL_0004)并没有真正检查handle
引用等于(或不等于)null (ldnull
)。
相反,它直接在 handle
引用上调用 brfalse
。
换句话说,它一开始并没有做这样的事情:
...
IL_0001: ldarg.1 // handle
IL_0002: ldnull
IL_0003: ceq
IL_0004: brfalse.s IL_0012 // transfer to line that stores into _handle
...
当然,第二种方法会生成更多的IL代码,但为了这个问题,我只输入了相关部分。
问题:
1)这两种方法之间是否存在任何可测量的差异?
第一种方法(编译器对上面的 C# 代码执行的操作)直接在对象引用上调用 brtrue/false)
,而第二种方法则在对象与 ldnull
的比较结果(这是编译器通过 == null
检查生成的结果)。
据我了解,第一种方法至少少处理一条指令,因此在某种程度上,这是可以衡量的。
2) 如果是这样,有没有办法强制编译器为 C# 中的空检查生成此类 IL?
即在 C# 中编写与 myObj != null
等效的代码,其中生成的 IL 直接在 myObj
引用上调用 brtrue.s
。
或者我错过了一些基本的东西吗? IL 不是我的最强项。
我也愿意接受有人合理地说服我,我只是在这里疯了:)
提前致谢!
最佳答案
正如评论中提到的,IL 只是代码执行时实际运行的一部分。因此,关于您问题的第一部分,ldnull
/ceq
版本较长这一事实自然意味着它需要稍微更长的时间加载到内存中,还有更多的指令供 JIT 分析并编译为 native 代码。
然而,鉴于差异非常微小,这些差异基本上可以忽略不计,并且由于它们是一次性事件,因此实际上与 JIT-ted 方法实际运行时的性能无关。为此,我们需要查看当该方法经过 JIT 处理时实际生成了哪些指令以及是否存在任何差异。
为此,我将两个动态方法组合在一起,显式发出所需的 IL,并检查 JIT 为两个版本生成的 native 代码。
结果可能毫不奇怪地相同(具体来说,测试 XXX,XXX
后跟 je ...
),因此显然 JIT 有能力识别这些结果IL 的模式,并在发出 native 代码时以相同的方式处理它们。
对于问题的第二部分,如 answer from InBetween表示一旦在 Release 模式下编译,生成的 IL 之间的差异就会消失,并且都使用较短的 brfalse
/brtrue
方法。
关于c# - 是否有 C# 代码可以直接在对象引用上调用 `brtrue`/`brfalse` 指令的空检查功能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51229737/