Rendimiento de métodos estáticos frente a métodos de instancia

108

Mi pregunta está relacionada con las características de rendimiento de los métodos estáticos frente a los métodos de instancia y su escalabilidad. Suponga para este escenario que todas las definiciones de clase están en un solo ensamblaje y que se requieren múltiples tipos de punteros discretos.

Considerar:

public sealed class InstanceClass
{
      public int DoOperation1(string input)
      {
          // Some operation.
      }

      public int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more instance methods.
}

public static class StaticClass
{
      public static int DoOperation1(string input)
      {
          // Some operation.
      }

      public static int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more static methods.
}

Las clases anteriores representan un patrón de estilo auxiliar.

En una clase de instancia, la resolución del método de instancia toma un momento para oponerse a StaticClass.

Mis preguntas son:

  1. Cuando mantener el estado no es una preocupación (no se requieren campos o propiedades), ¿es siempre mejor usar una clase estática?

  2. Donde hay un número considerable de estas definiciones de clases estáticas (digamos 100, por ejemplo, con varios métodos estáticos cada una), ¿afectará esto el rendimiento de ejecución o el consumo de memoria de manera negativa en comparación con el mismo número de definiciones de clases de instancia?

  3. Cuando se llama a otro método dentro de la misma clase de instancia, ¿sigue ocurriendo la resolución de la instancia? Por ejemplo, usando la palabra clave [this] como this.DoOperation2("abc")desde dentro DoOperation1de la misma instancia.

Bernie White
fuente
¿Qué quieres decir con "resolución de instancia"? En el nivel de IL, "este" puntero está disponible como cualquier otra variable local. De hecho, en algunas versiones antiguas de CLR / JIT, podría llamar a un método de instancia en un NULL siempre que no tocara el 'esto' - el código simplemente pasó volando y se bloqueó en nada ... ahora CLR / JIT contiene nulo explícito- comprobar en cada miembro invocar ..
quetzalcoatl
> vijaymukhi.com/documents/books/ilbook/chap8.htm y 'llamar instancia' versus solo 'llamar'. El primero espera "este" parámetro y el segundo, no.
quetzalcoatl
@Quetzalcoatl perdón por la confusión, la pregunta era más método a método de la misma instancia y si eso requiere que la instancia se resuelva por sí misma.
Bernie White
1
@quetzalcoatl Supuse que quería decir, "¿el compilador se deshace de comprobar que thisapunta a algo cuando una clase llama a un método de instancia sobre sí misma?"
Jon Hanna

Respuestas:

152

En teoría, un método estático debería funcionar ligeramente mejor que un método de instancia, en igualdad de condiciones, debido al thisparámetro oculto adicional .

En la práctica, esto hace tan poca diferencia que quedará oculto en el ruido de varias decisiones del compilador. (Por lo tanto, dos personas podrían "probar" una mejor que la otra con resultados en desacuerdo). Sobre todo porque thisnormalmente se pasa en un registro y, para empezar, a menudo se encuentra en ese registro.

Este último punto significa que, en teoría, deberíamos esperar que un método estático que toma un objeto como parámetro y hace algo con él sea un poco menos bueno que el equivalente como instancia en ese mismo objeto. Sin embargo, una vez más, la diferencia es tan pequeña que si intenta medirla probablemente terminará midiendo alguna otra decisión del compilador. (Especialmente porque la probabilidad de que esa referencia esté en un registro todo el tiempo también es bastante alta).

Las diferencias reales de rendimiento se reducirán a si tienes objetos en la memoria de forma artificial para hacer algo que naturalmente debería ser estático, o estás enredando cadenas de paso de objetos de formas complicadas para hacer lo que naturalmente debería ser una instancia.

Por lo tanto, para el número 1. Cuando mantener el estado no es una preocupación, siempre es mejor ser estático, porque para eso es estático . No es un problema de rendimiento, aunque hay una regla general de jugar bien con las optimizaciones del compilador: es más probable que alguien se haya esforzado por optimizar los casos que surgen con el uso normal que los que surgen con un uso extraño.

Número 2. No importa. Hay una cierta cantidad de costo por clase para cada miembro en términos de la cantidad de metadatos que hay, la cantidad de código que hay en el archivo DLL o EXE real y la cantidad de código jitted que habrá. Esto es lo mismo ya sea de instancia o estático.

Con el ítem 3, thises como thishace. Sin embargo nota:

  1. El thisparámetro se pasa en un registro particular. Al llamar a un método de instancia dentro de la misma clase, es probable que ya esté en ese registro (a menos que esté escondido y el registro se haya usado por alguna razón) y, por lo tanto, no se requiere ninguna acción para establecer el valor thisque debe establecerse. . Esto se aplica hasta cierto punto a, por ejemplo, que los dos primeros parámetros del método son los dos primeros parámetros de una llamada que realiza.

  2. Como quedará claro que thisno es nulo, esto se puede utilizar para optimizar las llamadas en algunos casos.

  3. Como quedará claro que thisno es nulo, esto puede hacer que las llamadas al método en línea sean más eficientes nuevamente, ya que el código producido para falsificar la llamada al método puede omitir algunas comprobaciones nulas que podría necesitar de todos modos.

  4. Dicho esto, los cheques nulos son baratos.

Vale la pena señalar que los métodos estáticos genéricos que actúan sobre un objeto, en lugar de los métodos de instancia, pueden reducir algunos de los costos discutidos en http://joeduffyblog.com/2011/10/23/on-generics-and-some-of- the-associated-overheads / en el caso de que esa estática dada no se llame para un tipo determinado. Como él dice: "Dejando de lado, resulta que los métodos de extensión son una excelente manera de hacer que las abstracciones genéricas sean más rentables".

Sin embargo, tenga en cuenta que esto se relaciona solo con la creación de instancias de otros tipos utilizados por el método, que de otra manera no existen. Como tal, realmente no se aplica a muchos casos (algún otro método de instancia usó ese tipo, algún otro código en otro lugar usó ese tipo).

Resumen:

  1. En su mayoría, los costos de rendimiento de instancia frente a estático son inferiores a insignificantes.
  2. Los costos que existen generalmente vendrán cuando se abuse de la estática, por ejemplo, o viceversa. Si no lo convierte en parte de su decisión entre estático e instancia, es más probable que obtenga el resultado correcto.
  3. Hay casos raros en los que los métodos genéricos estáticos en otro tipo dan como resultado la creación de menos tipos que los métodos genéricos de instancia, lo que puede hacer que a veces tenga un pequeño beneficio para convertirlos en usados ​​con poca frecuencia (y "rara vez" se refiere a los tipos con los que se usa en el vida útil de la aplicación, no con qué frecuencia se llama). Una vez que entienda de lo que está hablando en ese artículo, verá que de todos modos es 100% irrelevante para la mayoría de las decisiones estáticas frente a instancias. Editar: Y en su mayoría solo tiene ese costo con ngen, no con código jitted.

Editar: Una nota sobre cuán baratos son los controles nulos (que reclamé anteriormente). La mayoría de las comprobaciones de nulos en .NET no comprueban nulos en absoluto, sino que continúan con lo que iban a hacer con la suposición de que funcionará, y si ocurre una excepción de acceso, se convierte en un archivo NullReferenceException. Como tal, principalmente cuando conceptualmente el código C # implica una verificación nula porque está accediendo a un miembro de instancia, el costo si tiene éxito es en realidad cero. Una excepción serían algunas llamadas en línea (porque quieren comportarse como si llamaran a un miembro de instancia) y simplemente presionan un campo para activar el mismo comportamiento, por lo que también son muy baratas y, de todos modos, a menudo se pueden omitir. (por ejemplo, si el primer paso del método implicaba acceder a un campo tal como estaba).

Jon Hanna
fuente
¿Puede comentar si la pregunta estática vs instancia tiene alguna relación con la coherencia de la caché? ¿Es más probable que depender de uno u otro cause pérdidas de caché? ¿Existe un buen esquema que explique por qué?
scriptocalypse
@scriptocalypse No realmente. La caché de instrucciones no verá ninguna diferencia, y en ese nivel no hay mucha diferencia entre acceder a los datos a thistravés de un parámetro explícito. Un mayor impacto aquí sería qué tan cerca están los datos de los datos relacionados (los campos de tipo de valor o los valores de matriz están más cerca que los datos de los campos de tipo de referencia) y los patrones de acceso.
Jon Hanna
"en teoría, deberíamos esperar que un método estático que toma un objeto como parámetro y hace algo con él sea un poco menos bueno que el equivalente como instancia en ese mismo objeto". - ¿Quiere decir que si el método de muestra anterior toma parámetro como objeto en lugar de cadena, ¿no estático es mejor? por ejemplo: tengo mi método estático toma el objeto como parámetro y lo serializa en una cadena y devuelve una cadena. ¿Está sugiriendo utilizar no estático en este caso?
batmaci
1
@batmaci Quiero decir que hay una buena posibilidad de obj.DoSomehting(2)que sea un poco más barato que, DoSomething(obj, 2)pero como también dije, la diferencia es tan pequeña y tan dependiente de cosas pequeñas que podrían terminar siendo diferentes en la compilación final que realmente no vale la pena preocuparse en absoluto. Si está haciendo algo tan caro (en relación con el tipo de diferencias en juego aquí) como serializar algo en una cadena, entonces es especialmente inútil.
Jon Hanna
Hay una cosa, quizás obvia, pero importante, que falta en esta excelente respuesta: un método de instancia requiere una instancia, y crear una instancia no es barato. Incluso un valor predeterminado ctortodavía requiere la inicialización de todos los campos. Una vez que ya tenga una instancia, esta respuesta se aplica ("en igualdad de condiciones"). Por supuesto, un costo caro también cctorpuede hacer que los métodos estáticos sean lentos, pero eso es solo en la primera llamada y se aplica igualmente a los métodos de instancia. Consulte también docs.microsoft.com/en-us/previous-versions/dotnet/articles/…
Abel
8

Cuando mantener el estado no es una preocupación (no se requieren campos o propiedades), ¿es siempre mejor usar una clase estática?

Yo diría que sí. Al declarar algo static, declaras una intención de ejecución sin estado (no es obligatorio, pero una intención de algo que uno esperaría)

Donde hay un número considerable de estas clases estáticas (digamos 100 por ejemplo, con varios métodos estáticos cada una), ¿afectará esto el rendimiento de ejecución o el consumo de memoria de manera negativa en comparación con el mismo número de clases de instancia?

No lo crea, a menos que esté seguro de que las clases estáticas realmente no tienen stet, porque si no, es fácil estropear las asignaciones de memoria y tener pérdidas de memoria.

Cuando la palabra clave [this] se usa para llamar a otro método dentro de la misma clase de instancia, ¿todavía ocurre la resolución de la instancia?

No estoy seguro sobre este punto (este es un detalle puramente de implementación de CLR), pero piense que sí.

Tigran
fuente
Los métodos estáticos no se pueden burlar. Si realiza TDD o incluso solo pruebas unitarias, esto dañará mucho sus pruebas.
Trampster
@trampster ¿Por qué? Es solo una pieza de lógica. ¿Puedes burlarte fácilmente de lo que le das? Conseguir un comportamiento correcto. Y muchos métodos estáticos serán piezas de lógica privadas en una función de todos modos.
M. Mimpen
@ M.Mimpen siempre que lo deje en pequeñas piezas privadas, está bien, si es un método público y lo usa desde otros cierres y necesita cambiar lo que hace en su prueba, entonces está atascado, cosas como IO de archivos o acceso a la base de datos o llamadas de red, etc., si se coloca en un método estático, se volverá inmockable, a menos que, como usted dice, inyecte una dependencia simulada como parámetro para el método estático
trampster
-2

Los métodos estáticos son más rápidos pero menos OOP, si va a utilizar patrones de diseño, es probable que el método estático sea un código incorrecto, para escribir la lógica empresarial mejor sin estáticas, funciones comunes como lectura de archivos, WebRequest, etc. responder

LEGION_quema
fuente
16
No dio argumentos a sus afirmaciones.
ymajoros
2
@ fjch1997 2 votantes positivos parecen pensar lo contrario (por lo que vale). Se recomienda practicar comentarios negativos en stackexchange: meta.stackexchange.com/questions/135/…
ymajoros