2009-06-30 12 views
6

Trabajo en aplicaciones desarrolladas en C#/.NET con Visual Studio. Muy a menudo ReSharper, en los prototipos de mis métodos, me aconseja reemplazar el tipo de mis parámetros de entrada por otros más genéricos. Por ejemplo, List <> con IEnumerable <> si solo uso la lista con un foreach en el cuerpo de mi método. Puedo entender por qué parece más inteligente escribir eso, pero estoy bastante preocupado con el rendimiento. Temo que el rendimiento de mis aplicaciones disminuirá si escucho ReSharper ...Impacto en el rendimiento del cambio a interfaces genéricas

Puede alguien me explique con precisión (más o menos) lo que está sucediendo detrás de las escenas (es decir, en el CLR) cuando escribo:

public void myMethod(IEnumerable<string> list) 
{ 
    foreach (string s in list) 
    { 
    Console.WriteLine(s); 
    } 
} 

static void Main() 
{ 
    List<string> list = new List<string>(new string[] {"a", "b", "c"}); 
    myMethod(list); 
} 

y cuál es la diferencia con:

public void myMethod(List<string> list) 
{ 
    foreach (string s in list) 
    { 
    Console.WriteLine(s); 
    } 
} 

static void Main() 
{ 
    List<string> list = new List<string>(new string[] {"a", "b", "c"}); 
    myMethod(list); 
} 
+2

Ok, sé que esta es una discusión muy antigua, pero tal vez algo me guste como buscar ayuda sobre el mismo problema. De hecho, HAY una penalización de rendimiento en el uso de interfaces y de una manera que nunca había adivinado. Puse una descripción aquí (lo siento: alemán) en mi BLog: http://jochen.jochen-manns.de/index.php/2011/02/05/performance-von-schnittstellen-in-net-aka-the- good-the-bad-and-the-feo/ –

+0

@JMS: Me encantó ese artículo (desafortunadamente, no todos podrán leerlo, ya que está en alemán). Simplemente demuestra que la complejidad crea imprevisibilidad. También es un excelente ejemplo de lo que Jon Skeet llama "tendrías que profundizar en los detalles [...] de la nebulosa comprensión de JITting, thunk, vtables y cómo se aplican", etc. – sehe

Respuesta

11

Le preocupa el rendimiento, pero ¿tiene algún motivo para ello? Supongo que no ha comparado el código en absoluto. Siempre punto de referencia antes de reemplazar código legible y legible con más código de rendimiento.

En este caso, la llamada a Console.WriteLine dominará por completo el rendimiento de todos modos.

Aunque sospecho que puede haber un teórica diferencia en el rendimiento entre el uso de List<T> y IEnumerable<T> aquí, sospecho que el número de casos en los que es importante en aplicaciones del mundo real es extremadamente pequeña.

Ni siquiera es como si el tipo de secuencia se estuviera utilizando para muchas operaciones: hay una única llamada a GetEnumerator() que se declara que devuelve IEnumerator<T> de todos modos. A medida que la lista aumenta, cualquier diferencia en el rendimiento entre los dos obtendrá incluso más pequeño, ya que solo tendrá impacto en el inicio del ciclo.

Ignorando el análisis, lo que hay que sacar es medida rendimiento antes de basar las decisiones de codificación en él.

En cuanto a lo que sucede entre bastidores, tendrías que profundizar en los detalles de qué es exactamente en los metadatos en cada caso. Sospecho que en el caso de una interfaz hay un nivel extra de redirección, al menos en teoría: el CLR tendría que averiguar dónde estaba el vtable para el tipo de objeto de destino, IEnumerable<T>, y luego llamar al código del método apropiado. En el caso de List<T>, el JIT conocería el desplazamiento correcto en el vtable para comenzar, sin la búsqueda adicional. Esto solo se basa en mi comprensión un tanto nebulosa de JITting, thunk, vtables y cómo se aplican a las interfaces. Puede ser un poco incorrecto, pero lo más importante es un detalle de implementación.

+0

De hecho, particularmente en aplicaciones del mundo real donde si estás haciendo algo con redes, bases de datos o discos, es muy poco probable que algo como esto sea tu problema de rendimiento. –

+0

ok tal vez el hecho de que doy un ejemplo concreto oculta mi verdadera pregunta. Y tal vez mi pregunta no fue la buena. En realidad, tengo más curiosidad sobre el mecanismo del CLR que sobre el rendimiento en sí mismo; Obviamente, cuando escribe toneladas de código, el uso sistemático de la mayoría de los tipos genéricos y la interfaz no debería ser lo que más le cueste. – PierrOz

+0

En realidad, podría obtener un aumento de rendimiento al usar interfaces. Como CLR realiza el almacenamiento en caché de llamadas de interfaz, algo que no funcionará en llamadas regulares. – DiVan

1

En general, el aumento de la flexibilidad valdrá lo diferencia de rendimiento menor que incurriría.

1

El motivo básico para esta recomendación es crear un método que funcione en IEnumberable vs. List es flexibilidad futura. Si en el futuro necesita crear MySpecialStringsCollection, podría hacer que implemente el método IEnumerable y seguir utilizando el mismo método.

Esencialmente, creo que se reduce, a menos que notes un golpe de rendimiento significativo y significativo (y me sorprendería si hubieses notado alguno); prefiera una interfaz más tolerante que acepte más de lo que espera hoy.

3

Tendría que mirar el código generado para estar seguro, pero en este caso, dudo que haya mucha diferencia. La instrucción foreach siempre opera en un IEnumerable o IEnumerable<T>. Incluso si especifica List<T>, aún tendrá que obtener el IEnumerable<T> para iterar.

+2

En realidad, la declaración foreach * doesn 't * requiere un IEnumerable o IEnumerable para operar. El tipo solo tiene que tener un método GetEnumerator() que devuelve algo con un método apropiado MoveNext() y la propiedad Current. Pero en este caso, por supuesto, la Lista * no * implementa IEnumerable . –

+0

Además, si el usuario tiene una mejor manera de hacer las cosas si se le da una Lista o Colección , es libre de comprobar si el IEnumerable es una de esas clases y un caso especial para mejorar el rendimiento. – user7116

+0

Aunque por lo general sería mejor que comprueben si hay IList e ICollection :) –

1

En la primera versión (IEnumerable) es más genérico y en realidad dice que el método acepta cualquier argumento que implemente esta interfaz.

La segunda versión restringe el método para aceptar el tipo de clase específico y esto no se recomienda en absoluto. Y el rendimiento es casi el mismo.

0

Una interfaz simplemente define la presencia y firma de los métodos y propiedades públicos implementados por la clase. Dado que la interfaz no "se sostiene por sí misma", debe haber no diferencia de rendimiento para el método en sí, y cualquier penalización de "fundición" - si existe - debería ser casi demasiado pequeña para medir.

1

La definición para List<T> es:

[SerializableAttribute] 
public class List<T> : IList<T>, ICollection<T>, 
    IEnumerable<T>, IList, ICollection, IEnumerable 

Así List<T> se deriva de IList, ICollection, IList<T>, y ICollection<T>, además de IEnumerable y IEnumerable<T>.

La interfaz IEnumerable expone el método de GetEnumerator que devuelve un IEnumerator , un método MoveNext y una propiedad Current. Estos mecanismos son los que utiliza la clase List<T> para recorrer la lista con foreach y luego.

De ello se deduce que, si IList, ICollection, IList<T>, and ICollection<T> no están obligados a hacer el trabajo, entonces es sensato utilizar IEnumerable o IEnumerable<T> en su lugar, lo que elimina la plomería adicional.

0

No hay una penalización de rendimiento para un upcast estático. Es una construcción lógica en el texto del programa.

Como han dicho otras personas, la optimización prematura es la raíz de todo mal. Escriba su código, ejecútelo a través de un análisis de punto de acceso antes de preocuparse por el ajuste del rendimiento de las cosas.

0

Ingresar en IEnumerable <> puede crear algunos problemas, ya que podría recibir alguna expresión LINQ con ejecución diferida o retorno de rendimiento. En ambos casos, no tendrá una colección sino algo que podría repetir. Entonces, cuando desee establecer algunos límites, puede solicitar una matriz. No hay problema para llamar a collection.ToArray() antes de pasar el parámetro, pero se asegurará de que no haya advertencias diferidas ocultas allí.

2

En general, diría que si usted es reemplazar la interfaz no genérico equivalente genérico por el sabor (por ejemplo IList<> ->IList<T>) que están obligados a obtener un mejor rendimiento o equivalente.

Un punto de venta único es que, a diferencia de Java,.NET no utiliza borrado de tipo y admite tipos de valores verdaderos (struct), una de las principales diferencias estaría en cómo almacena, p. a List<int> internamente. Esto podría convertirse rápidamente en una gran diferencia dependiendo de qué tan intensamente se use la Lista.


Un punto de referencia sintético braindead mostró:

for (int j=0; j<1000; j++) 
    { 
     List<int> list = new List<int>(); 
     for (int i = 1<<12; i>0; i--) 
      list.Add(i); 

     list.Sort(); 
    } 

a ser más rápido por un factor de 3.2x que el semi-equivalente no genérico:

for (int j=0; j<1000; j++) 
    { 
     ArrayList list = new ArrayList(); 
     for (int i = 1<<12; i>0; i--) 
      list.Add(i); 

     list.Sort(); 
    } 

de exención de responsabilidad Me doy cuenta de que este punto de referencia es sintético, en realidad no se centra en el uso de interfaces allí mismo (envía directamente llamadas a métodos virtuales en un tipo específico) etc. Sin embargo, ilustra el punto que estoy haciendo. No temas los genéricos (al menos no por motivos de rendimiento).

Cuestiones relacionadas