c# performance coding-style equality readability

c# - ¿Igualdad de referencia en la diferencia de rendimiento?((objeto) obj1==(objeto) obj2) vs. object.ReferenceEquals(obj1, obj2)



performance coding-style (6)

¿Existe una sobrecarga adicional en el uso del método object.ReferenceEquals

No. El método contiene directamente la descripción mínima de IL para realizar la verificación de igualdad de referencia (para el registro: es equivalente al operador VB''s Is ) y, a menudo, el JIT (especialmente cuando se dirige a x64) lo integrará, por lo que no hay sobrecarga.

Acerca de la legibilidad: personalmente pienso que object.ReferenceEquals es potencialmente más legible (incluso en forma negada) porque expresa explícitamente su semántica. El elenco para object puede ser confuso para algunos programadores.

Acabo de encontrar un artículo discutiendo esto. Prefiere (object)x == y porque la huella de IL es más pequeña. Argumenta que esto podría facilitar la incorporación del método X utilizando esta comparación. Sin embargo (sin ningún conocimiento detallado del JIT pero de manera lógica e intuitiva) creo que esto está mal: si el JIT se comporta como un compilador de C ++ optimizado, considerará el método después de incluir la llamada a ReferenceEquals , por lo que (por el bien de la integración método X ) la huella de memoria será exactamente igual en ambos sentidos.

Es decir, elegir una forma sobre la otra no tendrá ningún impacto en el JIT y, por consiguiente, en el rendimiento.

¿Existe una sobrecarga adicional en el uso de los versos del método object.ReferenceEquals usando ((object)obj1 == (object)obj2) ?

En el primer caso, habría una llamada al método estático involucrada, y en ambos casos, estaría involucrada alguna forma de conversión a un objeto.

Incluso si el compilador equilibra esos métodos, ¿qué pasa con la desigualdad?

(object)obj != null

en comparación con...

!object.ReferenceEquals(obj,null)

Supongo que en algún momento, se producirá una negación lógica, ya sea dentro del operador! =, O según se aplique al resultado del método ReferenceEquals. ¿Qué piensas?

También está el tema de la legibilidad a considerar. ReferenceEquals parece más claro cuando se comprueba la igualdad, pero para la desigualdad, ¡uno podría pasar por alto el ! object.ReferenceEquals precedente, mientras que el != en la primera variación es difícil de pasar por alto.


Agregando mis dos centavos, después de muchas horas de retraso en el código crítico, en bases de código muy grandes con profundidades de llamada a veces locas y profundas.

Fuera de un ''micro benchmark'' en el mundo real, el JIT tiene muchos más problemas e inquietudes, y ni tiene el lujo del tiempo de compilación C ++ WPO, ni la facilidad de los compiladores de C # traducciones más directas, y aún así todos los problemas de no Es necesario tener todo el contexto después de que se haga el compilador de C #.

Las formas ''pedantes'':

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

Si realmente tiene problemas de rendimiento de honesto a dios pesados ​​y medidos, o necesita eliminar la presión del JIT para unas pocas clases realmente importantes, esto puede ayudar mucho. Lo mismo sucede con NullOrEmpty vs ''(object) str == null || str.Length == 0 ''.

Al no tener el lujo de WPO, y todas las limitaciones de no saber en muchos casos, qué ensamblajes pueden cargarse o descargarse después de haber recibido un golpe en JITing, suceden cosas no deterministas con respecto a lo que se optimiza y cómo.

Este es un gran tema, pero aquí hay algunos puntos:

  1. El JIT perseguirá la inclusión y registrará la optimización de la llamada entrante hasta ahora , y depende totalmente de lo que esté sucediendo en ese momento. Si terminas compilando una función en la cadena una vez debido al uso, y una más abajo en la cadena una ejecución diferente, puedes obtener resultados netamente diferentes. Lo peor para muchas aplicaciones que están limitadas por la latencia o la UI es el no depterminismo, y en aplicaciones más grandes esto puede sumarse rápidamente.

  2. ! ((objeto) a == (objeto) b) y (objeto) a! = (objeto) b no siempre se compilan en el mismo código , como es cierto para certianly (a == b) y a! = b, incluso sin operadores explícitos o reemplazos de Igualdad. A ''(objeto) a! = (Objeto) b'' es mucho más probable que active la llamada más pedante de .Net hacia el tiempo de ejecución, que es muy costoso.

  3. La protección temprana y con frecuencia con ''(objeto)'' o ''Reemercales'' es muy útil para el JIT incluso si actualmente no tiene operadores o sustituciones de Igualdad. (objeto) es aún mejor. Si piensa en lo que tiene que hacer el JIT para determinar si un tipo podría tener anulaciones, y lidiar con las reglas de igualdad bizantine (sp) y todo eso, es como un mini infierno, y cualquier cosa que pueda hacerse pública más adelante y tiene la intención de ref. Igualdad, te salvas de una desaceleración repentina o un código malicioso más tarde. Si ya es público y no está sellado, el JIT no puede esperar que las reglas tengan tiempo para perseguirlas.

  4. La protección de los chequeos ''nulos'' generalmente más generalizados es probablemente aún más importante , aunque no es parte de la pregunta del OP, ya que generalmente se aplican las mismas reglas y problemas. ''(objeto) a == nulo'' y ''! ((objeto) a == nulo)'' siendo los equivalentes ''pedantes''.


Contrariamente a las respuestas aquí, encontré (object) == más rápido que object.ReferenceEquals . En cuanto a cómo más rápido, muy despreciable!

Banco de pruebas:

Sé que necesita una verificación de igualdad de referencia, pero estoy incluyendo el object.Equals(,) estático también en el caso de clases donde no se anula.

Plataforma: x86; Configuración: versión de lanzamiento

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()); }

Prueba:

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(,) llama a ReferenceEquals internamente y, si no son iguales, llamaría al método Equals virtual Equals de la clase y, por lo tanto, verá la diferencia de velocidad.

Los resultados fueron consistentes en la configuración de Debug también ...

Como se señaló, el énfasis debe estar en la legibilidad / significado / intención reveladora.


El artículo mencionado anteriormente acerca de que el operador == sea ​​mejor proporciona información incompleta, al menos en .NET 4.0 (bueno, fue escrito en 2.0 veces).

Indica que ReferenceEquals no está siendo optimizado / en línea, lo cual es cierto solo si construye su proyecto con la configuración ''AnyCPU''. La configuración en ''x86'' o ''x64'' hace que (objeto) obj == null y ReferenceEquals (objeto, nulo) terminen siendo IL idénticos, donde ambos métodos son simplemente una instrucción ''ceq'' IL.

Entonces la respuesta es: la IL producida por ambos métodos es idéntica en las versiones Release / x86 o x64.

ReferenceEquals es definitivamente más legible, al menos a mi gusto.


La sobrecarga de Object.ReferenceEquals solo se realiza al cargar los argumentos, que se eliminarán en la mayoría de los escenarios. Después de eso, tanto Object.ReferenceEquals como operator == se reducen a un solo operador de ceqq. En el peor de los casos, la diferencia será insignificante.

Más importante aún, Object.ReferenceEquals es mucho más revelador de intenciones que (object) o1 == (object) o2. Dice claramente en el código "Estoy probando la igualdad / identidad de referencia", en lugar de ocultar la intención bajo un montón de moldes.