参考平等性能差异? ((object)obj1 ==(object)obj2)vs。object.ReferenceEquals(obj1,obj2)

时间:2009-04-09 19:17:33

标签: c# performance coding-style equality readability

使用object.ReferenceEquals方法使用((object)obj1 == (object)obj2)时会有额外的开销吗?

在第一种情况下,将涉及一个静态方法调用,并且在这两种情况下都会涉及到对象的某种形式的转换。

即使编译器平衡了这些方法,不平等又如何呢?

(object)obj != null

与......相比。

!object.ReferenceEquals(obj,null)

我认为在某些时候,会出现逻辑否定,无论是在!=运算符内,还是应用于ReferenceEquals方法的结果。你觉得怎么样?

还有可读性问题需要考虑。在检查相等性时,ReferenceEquals看起来更清晰,但对于不平等,可能会错过!之前的 object.ReferenceEquals ,而 != 第一种变化很难被忽视。

6 个答案:

答案 0 :(得分:22)

  

使用object.ReferenceEquals方法是否有额外的开销

没有。该方法直接包含最小IL描述来执行引用相等性检查(对于记录:它相当于VB的Is运算符)并且通常由JIT内联(特别是在定位x64时),因此开销。

关于可读性:我个人认为object.ReferenceEquals可能更具可读性(即使是否定形式),因为它明确表达了它的语义。对object的强制转换可能会让一些程序员感到困惑。

我刚刚发现an article正在讨论这个问题。它更喜欢(object)x == y因为IL足迹较小。它认为这可能有助于使用此比较来内联方法X。但是(没有任何关于JIT的详细知识,但在逻辑上和直觉上)我认为这是错误的:如果JIT表现得像优化C ++编译器,它将在内联调用{{>后考虑方法1}},所以(为了内联方法ReferenceEquals)内存占用将完全相同。

也就是说:选择一种方式而不是另一种方式对JIT没有任何影响,从而影响性能。

答案 1 :(得分:5)

与此处的答案相反,我发现(object) ==object.ReferenceEquals更快。至于速度有多快,可以忽略不计!

试验台:

我知道你需要引用相等性检查,但我也包括静态object.Equals(,)方法,以防它的类没有被覆盖。

  

平台:x86;配置:发布版本

class Person {
}

public static void Benchmark(Action method, int iterations = 10000)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
        method();

    sw.Stop();
    MsgBox.ShowDialog(sw.Elapsed.TotalMilliseconds.ToString());
}

测试:

Person p1 = new Person();
Person p2 = new Person();
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //~ 1250ms
    b = object.Equals(p1, p2); //2100ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~4000ms

}, 100000000);

Person p1 = new Person();
Person p2 = null;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //990 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); // 1230 ~ 1260ms
    b = object.Equals(p1, p2); //1250 ~ 1300ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~3100ms

}, 100000000);

Person p1 = null;
Person p2 = null;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //1260 ~ 1270ms
    b = object.Equals(p1, p2); //1180 ~ 1220ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //~3100ms

}, 100000000);

Person p1 = new Person();
Person p2 = p1;
bool b;
Benchmark(() =>
{
    b = (object)p1 == (object)p2; //960 ~ 1000ms
    b = object.ReferenceEquals(p1, p2); //1260 ~ 1280ms
    b = object.Equals(p1, p2); //1150 ~ 1200ms
    b = EqualityComparer<Person>.Default.Equals(p1, p2); //3700 ~ 3800ms

}, 100000000);

object.Equals(,)在内部调用ReferenceEquals,如果它们不相等,则会调用该类的覆盖虚拟Equals方法,因此您会看到速度差异的通知。

结果在Debug配置中也是一致的......

正如所指出的,重点应放在可读性/意义/揭示意图上。

答案 2 :(得分:3)

Object.ReferenceEquals的开销仅在加载参数时,在大多数情况下都会被JIT实现。之后,Object.ReferenceEquals和operator ==都归结为单个IL ceq运算符。在最坏的情况下,差异将是微不足道的。

更重要的是,Object.ReferenceEquals比(object)o1 ==(object)o2更有意图揭示。它在代码“我正在测试引用相等/身份”中清楚地说明,而不是将意图隐藏在一堆强制转换下。

答案 3 :(得分:3)

在非常大的代码库中添加我的两分钱,在有关疯狂的深度调用深度的非常大的代码库之后。

在现实世界中的“微观基准”之外,JIT还有更多的问题和关注点,而且编译时C ++ WPO的编程时间也很少,而且C#编译器的易用性也更直接,而且所有的在C#编译器完成后,没有必要拥有所有上下文的问题。

'迂腐'形式:

if ((object)a == (object)b) { }     // ref equals

if (!((object)a == (object)b)) { }  // ref not equals

如果你真的有诚实的上帝性能问题权衡和测量,或者需要从JIT中为一些非常普遍的类别施加压力,这可能会有所帮助。 NullOrEmpty vs'(object)str == null ||也是如此str.Length == 0'。

没有WPO的奢侈品,以及在许多情况下都不知道的所有限制因素,在JITing遭遇重击之后可能会加载或卸载哪些程序集,奇怪的非确定性事情就会被优化以及如何进行

这是一个很大的话题,但这里有几点:

  1. 到目前为止,JIT将追逐内联和优化注册深度调用深度,完全取决于当时还有什么。如果由于使用而最终在链上编译一次函数,并且在链的另一端进行不同的运行,则可以获得截然不同的结果。对于受延迟或UI驱动约束的许多应用程序而言,最糟糕的事情是非删减,而在较大的应用程序中,这可能会很快加起来。

  2. !((object)a ==(object)b)和(object)a!=(object)b并不总是编译成相同的代码,因为它是真的certianly for!(a == b)和a!= b,即使没有任何显式运算符或Equals覆盖。一个'(对象)a!=(对象)b'更有可能触发.Net自己更加迂腐的调用运行时非常昂贵。

  3. 即使您目前没有操作符或等号覆盖,也可以提前和经常使用'(object)'或'RefEquals'保护,如果对JIT非常有帮助。 (对象)更好。如果你考虑JIT必须做什么来确定一个类型是否可以覆盖,并处理bizantine(sp)Equality规则等等,它就像一个迷你地狱,以及任何可以在以后公开的任何东西,你打算参考平等,你可以避免突然放缓或以后的代码。如果它已经是公开的而不是密封的,那么JIT就不能保证规则会或者有时间追查它们。

  4. 保护通常更为普遍的“无效”检查可能更为重要,虽然不是OP问题的一部分,因为相同的规则和问题通常适用。 '(object)a == null'和'!((object)a == null)'是'迂腐'的等价物。

答案 4 :(得分:2)

前面提到的article about == operator更好地提供了不完整的信息,至少在.NET 4.0上是这样的(它已经被写回2.0次了。)

它表示ReferenceEquals没有被优化/内联,只有在使用“AnyCPU”配置构建项目时才会出现这种情况。设置为'x86'或'x64'使(对象)obj == null和ReferenceEquals(object,null)最终成为相同的IL,其中两个方法都只是一个'ceq'IL指令。

所以答案是:两种方法生成的IL在Release / x86或x64版本上是相同的。

ReferenceEquals肯定更具可读性,至少符合我的口味。

答案 5 :(得分:0)

public static bool ReferenceEquals (Object objA, Object objB) {
        return objA == objB;
    }

http://referencesource.microsoft.com/#mscorlib/system/object.cs,4d607d6d56a93c7e