[c#] Uso apropiado de 'rendimiento rendimiento'



Answers

Llenar una lista temporal es como descargar todo el video, mientras que usar el yield es como transmitir ese video.

Question

La palabra clave yield es una de esas keywords en C # que me sigue desconcertando, y nunca he tenido la certeza de que la estoy usando correctamente.

De los siguientes dos códigos, ¿cuál es el preferido y por qué?

Versión 1: Uso del retorno de rendimiento

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        foreach (Product product in products)
        {
            yield return product;
        }
    }
}

Versión 2: devolver la lista

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList<Product>();
    }
}



Hubiera usado la versión 2 del código en este caso. Como tiene la lista completa de productos disponibles y eso es lo que espera el "consumidor" de esta llamada a un método, se le solicitará que envíe la información completa a la persona que llama.

Si el que llama de este método requiere "una" información a la vez y el consumo de la siguiente información es bajo demanda, entonces sería beneficioso usar el retorno de rendimiento que asegurará que el comando de ejecución se devolverá a la persona que llama cuando una unidad de información está disponible.

Algunos ejemplos en los que uno podría usar el retorno de rendimiento es:

  1. Cálculo complejo, paso a paso, donde la persona que llama está esperando los datos de un paso a la vez
  2. Paginación en GUI - donde el usuario nunca puede llegar a la última página y solo se requiere que se revele un subconjunto de información en la página actual

Para responder a sus preguntas, habría utilizado la versión 2.




Esto es un poco más allá del punto, pero ya que la pregunta está etiquetada como las mejores prácticas, seguiré adelante y daré mi granito de arena. Para este tipo de cosas, prefiero convertirlo en una propiedad:

public static IEnumerable<Product> AllProducts
{
    get {
        using (AdventureWorksEntities db = new AdventureWorksEntities()) {
            var products = from product in db.Product
                           select product;

            return products;
        }
    }
}

Claro, es un poco más placa de caldera, pero el código que usa esto se verá mucho más limpio:

prices = Whatever.AllProducts.Select (product => product.price);

vs

prices = Whatever.GetAllProducts().Select (product => product.price);

Nota: No haría esto por ningún método que tarde un tiempo en hacer su trabajo.




Los dos pedazos de código realmente están haciendo dos cosas diferentes. La primera versión atraerá miembros a medida que los necesite. La segunda versión cargará todos los resultados en la memoria antes de que empieces a hacer algo con ella.

No hay una respuesta correcta o incorrecta a esta. Cuál es preferible solo depende de la situación. Por ejemplo, si hay un límite de tiempo para completar su consulta y necesita hacer algo semi complicado con los resultados, la segunda versión podría ser preferible. Pero tenga cuidado con los grandes resultados, especialmente si está ejecutando este código en modo de 32 bits. Me han mordido las excepciones OutOfMemory varias veces al hacer este método.

Sin embargo, la clave a tener en cuenta es esta: las diferencias están en la eficiencia. Por lo tanto, probablemente deberías elegir el que simplifique tu código y cambiarlo solo después del perfil.




Esto parecerá una sugerencia extraña, pero aprendí a usar la palabra clave yield en C # leyendo una presentación sobre generadores en Python: http://www.dabeaz.com/generators/Generators.pdf David M. Beazley. No necesita saber mucho de Python para entender la presentación, no lo hice. Lo encontré muy útil para explicar no solo cómo funcionan los generadores, sino por qué debería importarte.




El rendimiento puede ser muy poderoso para los algoritmos donde se necesita iterar a través de millones de objetos. Considere el siguiente ejemplo en el que necesita calcular posibles viajes para compartir el viaje. Primero generamos posibles viajes:

    static IEnumerable<Trip> CreatePossibleTrips()
    {
        for (int i = 0; i < 1000000; i++)
        {
            yield return new Trip
            {
                Id = i.ToString(),
                Driver = new Driver { Id = i.ToString() }
            };
        }
    }

Luego itere a través de cada viaje:

    static void Main(string[] args)
    {
        foreach (var trip in CreatePossibleTrips(trips))
        {
            // possible trip is actually calculated only at this point, because of yield
            if (IsTripGood(trip))
            {
                // match good trip
            }
        }
    }

Si usa List en lugar de yield, necesitará asignar 1 millón de objetos a la memoria (~ 190 mb) y este simple ejemplo tardará ~ 1400ms en ejecutarse. Sin embargo, si usa el rendimiento, no necesita poner todos estos objetos temporales en la memoria y obtendrá una velocidad de algoritmo significativamente más rápida: este ejemplo solo tardará ~ 400 ms en ejecutarse sin consumo de memoria.




Suponiendo que sus productos de la clase LINQ utilicen un rendimiento similar para enumerar / iterar, la primera versión es más eficiente porque solo arroja un valor cada vez que se itera.

El segundo ejemplo es convertir el enumerador / iterador a una lista con el método ToList (). Esto significa que itera manualmente sobre todos los elementos en el enumerador y luego devuelve una lista plana.




El uso del rendimiento es similar al retorno de la palabra clave, excepto que devolverá un generator . Y el objeto generador solo atravesará una vez .

el rendimiento tiene dos beneficios:

  1. No necesita leer estos valores dos veces;
  2. Puede obtener muchos nodos secundarios pero no tiene que ponerlos todos en la memoria.

Hay otra explanation clara que quizás te ayude.




Links