Pregunta Uso apropiado de 'rendimiento rendimiento'


los rendimiento palabra clave es uno de esos palabras clave en C # que me sigue desconcertando, y nunca he tenido la certeza de que lo estoy usando correctamente.

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

Versión 1: Utilizando 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: Devuelve 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>();
    }
}

792
2018-01-03 22:43


origen


Respuestas:


Tiendo a usar rendimiento-rendimiento cuando calculo el siguiente artículo en la lista (o incluso el siguiente grupo de artículos).

Usando su Versión 2, debe tener la lista completa antes de regresar. Al usar rendimiento-retorno, solo necesita tener el siguiente ítem antes de regresar.

Entre otras cosas, esto ayuda a extender el costo computacional de cálculos complejos en un marco de tiempo más grande. Por ejemplo, si la lista está conectada a una GUI y el usuario nunca va a la última página, nunca se calculan los elementos finales en la lista.

Otro caso donde el rendimiento-retorno es preferible es si el IEnumerable representa un conjunto infinito. Considere la lista de números primos, o una lista infinita de números aleatorios. Nunca puede devolver el IEnumerable completo de una vez, por lo que utiliza yield-return para devolver la lista de forma incremental.

En tu ejemplo particular, tienes la lista completa de productos, así que usaría la Versión 2.


725
2018-01-03 23:01



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


535
2017-07-08 14:14



Como un ejemplo conceptual para entender cuándo deberías usar yield, digamos el método ConsumeLoop() procesa los elementos devueltos / cedidos por ProduceList():

void ConsumeLoop() {
    foreach (Consumable item in ProduceList())        // might have to wait here
        item.Consume();
}

IEnumerable<Consumable> ProduceList() {
    while (KeepProducing())
        yield return ProduceExpensiveConsumable();    // expensive
}

Sin yield, la llamada a ProduceList() puede tomar mucho tiempo porque debe completar la lista antes de regresar:

//pseudo-assembly
Produce consumable[0]                   // expensive operation, e.g. disk I/O
Produce consumable[1]                   // waiting...
Produce consumable[2]                   // waiting...
Produce consumable[3]                   // completed the consumable list
Consume consumable[0]                   // start consuming
Consume consumable[1]
Consume consumable[2]
Consume consumable[3]

Utilizando yield, se reorganiza, trabajando de forma similar "en paralelo":

//pseudo-assembly
Produce consumable[0]
Consume consumable[0]                   // immediately Consume
Produce consumable[1]
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next

Y, por último, como muchos ya lo han sugerido, debería usar la Versión 2 porque de todos modos ya tiene la lista completa.


59
2017-10-09 21:45



Esto parecerá una sugerencia extraña, pero aprendí a usar el yield palabra clave en C # leyendo una presentación sobre generadores en Python: David M. Beazley http://www.dabeaz.com/generators/Generators.pdf. 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.


26
2018-01-04 21:51



Sé que esta es una vieja pregunta, pero me gustaría ofrecer un ejemplo de cómo la palabra clave yield puede ser utilizada de forma creativa. yo tengo De Verdad beneficiado de esta técnica. Espero que esto ayude a cualquier persona que tropiece con esta pregunta.

Nota: No piense en la palabra clave yield como una mera manera de crear una colección. Una gran parte del poder del rendimiento proviene del hecho de que la ejecución es pausado en tus método o propiedad hasta que el código de llamada itere sobre el siguiente valor. Aquí está mi ejemplo:

Usando la palabra clave yield (junto con Rob Eisenburg's Caliburn.Micro corutinasimplementación) me permite expresar una llamada asíncrona a un servicio web como este:

public IEnumerable<IResult> HandleButtonClick() {
    yield return Show.Busy();

    var loginCall = new LoginResult(wsClient, Username, Password);
    yield return loginCall;
    this.IsLoggedIn = loginCall.Success;

    yield return Show.NotBusy();
}

Lo que esto hará es encender mi BusyIndicator, llamar al método de inicio de sesión en mi servicio web, establecer mi indicador IsLoggedIn en el valor de retorno y luego apagar el BusyIndicator.

Así es como funciona esto: IResult tiene un método Execute y un evento Completed. Caliburn.Micro toma el IEnumerator de la llamada a HandleButtonClick () y lo pasa a un método Coroutine.BeginExecute. El método BeginExecute comienza a iterar a través de IResults. Cuando se devuelve el primer IResult, la ejecución se pausa dentro de HandleButtonClick (), y BeginExecute () asocia un controlador de eventos al evento Completado y llama a Execute (). IResult.Execute () puede realizar una tarea sincrónica o asincrónica y desencadena el evento Completado cuando termina.

LoginResult se ve así:

public LoginResult : IResult {
    // Constructor to set private members...

    public void Execute(ActionExecutionContext context) {
        wsClient.LoginCompleted += (sender, e) => {
            this.Success = e.Result;
            Completed(this, new ResultCompletionEventArgs());
        };
        wsClient.Login(username, password);
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
    public bool Success { get; private set; }
}

Puede ayudar establecer algo así y pasar por la ejecución para ver lo que está sucediendo.

¡Espero que esto ayude a alguien! Realmente he disfrutado explorando las diferentes maneras en que se puede usar el rendimiento.


24
2018-05-18 04:36



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 empiezas a hacer cualquier cosa con eso.

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.


12
2018-01-03 23:30



El rendimiento tiene dos grandes usos

Ayuda a proporcionar iteraciones personalizadas sin crear colecciones temp. (cargando todos los datos y bucles)

Ayuda a hacer una iteración con estado. ( transmisión)

A continuación se muestra un video simple que he creado con demostración completa para apoyar los dos puntos anteriores

http://www.youtube.com/watch?v=4fju3xcm21M


10
2018-04-05 18:48



Esto es lo que Chris vende habla sobre esas declaraciones en El lenguaje de programación C #;

A veces me olvido de que el rendimiento no es lo mismo que el rendimiento, en   que el código después de un retorno de rendimiento se puede ejecutar. Por ejemplo, el   el código después de la primera devolución aquí nunca se puede ejecutar:

    int F() {
return 1;
return 2; // Can never be executed
}

Por el contrario, el código después del primer retorno de rendimiento aquí puede ser   ejecutado:

IEnumerable<int> F() {
yield return 1;
yield return 2; // Can be executed
}

Esto a menudo me muerde en una declaración if:

IEnumerable<int> F() {
if(...) { yield return 1; } // I mean this to be the only
// thing returned
yield return 2; // Oops!
}

En estos casos, recordar que el rendimiento no es "final" como   el regreso es útil.


10
2018-05-27 20:18



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.


9
2017-11-22 16:35



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.


8
2018-01-03 22:58



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.


8
2018-01-03 23:30