11

Consideremos el siguiente código C#:C# Comprehensiones de listas = ¿azúcar sintáctico puro?

IEnumerable numbers = Enumerable.Range(0, 10); 
var evens = from num in numbers where num % 2 == 0 select num; 

¿Es este azúcar sintáctica pura para permitir que escriba un bucle for o foreach como una sola línea? ¿Hay alguna optimización del compilador bajo las cubiertas que hace que la comprensión de la lista anterior sea más eficiente que la construcción del bucle? ¿Cómo funciona esto bajo el capó?

Respuesta

14

Como dijo Jason, su código es equivalente a:

Enumerable.Range(0, 10).Where(n => n % 2 == 0); 

Nota lambda se transformará en una llamada de función que se realiza para cada elemento. Esta es probablemente la mayor parte de la sobrecarga. Hice una prueba, lo que indica LINQ es aproximadamente 3 veces más lentas (GMC mono versión 1.2.6.0) en esta tarea exacta

 
    Time for 10000000 for loop reps: 00:00:17.6852560 
    Time for 10000000 LINQ reps: 00:00:59.0574430 

    Time for 1000000 for loop reps: 00:00:01.7671640 
    Time for 1000000 LINQ reps: 00:00:05.8868350 

EDIT: Gishu informa que VS2008 y SP1 marco v3.5 da:

 
    Time for 1000000 loop reps: :00.3724585 
    Time for 1000000 LINQ reps: :00.5119530 

LINQ es aproximadamente 1,4 veces más lento allí.

Compara un bucle for y una lista con LINQ (y cualquier estructura que use internamente). De cualquier forma, convierte el resultado en una matriz (necesaria para forzar a LINQ a dejar de ser "flojo"). Ambas versiones repiten:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 

public class Evens 
{ 
    private static readonly int[] numbers = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 

    private static int MAX_REPS = 1000000; 

    public static void Main() 
    { 
     Stopwatch watch = new Stopwatch(); 

     watch.Start(); 
     for(int reps = 0; reps < MAX_REPS; reps++) 
     { 
      List<int> list = new List<int>(); // This could be optimized with a default size, but we'll skip that. 
      for(int i = 0; i < numbers.Length; i++) 
      { 
       int number = numbers[i]; 
       if(number % 2 == 0) 
        list.Add(number); 
      } 
      int[] evensArray = list.ToArray(); 
     } 
     watch.Stop(); 
     Console.WriteLine("Time for {0} for loop reps: {1}", MAX_REPS, watch.Elapsed); 

     watch.Reset(); 
     watch.Start(); 
     for(int reps = 0; reps < MAX_REPS; reps++) 
     { 
      var evens = from num in numbers where num % 2 == 0 select num; 
      int[] evensArray = evens.ToArray(); 
     } 
     watch.Stop(); 
     Console.WriteLine("Time for {0} LINQ reps: {1}", MAX_REPS, watch.Elapsed); 
    } 
} 

pruebas de rendimiento anterior en tareas similares (por ejemplo LINQ vs Loop - A performance test) corroborar esto.

+0

¿Estás ejecutando esto en Mono? ¿Estás seguro de que esto es comparable a Microsoft IL? –

+2

Mono usa MSIL, que también se conoce como CIL después de la estandarización. –

+2

Sí, pero eso no significa que los dos compiladores están creando resultados equivalentes. –

5

puede simplificar el código aún más por

var evens = Enumerable.Range(0, 10).Where(n => n % 2 == 0); 

Una ventaja de esta forma es que la ejecución de esta expresión se difiere hasta evens se repiten a lo largo (foreach(var n in evens) { ... }). La declaración anterior simplemente le dice al compilador que capture la idea de cómo enumerar los números pares entre 0 y 10, pero no ejecute esa idea hasta que sea absolutamente necesario.

1

En su código anterior, tiene una consulta Linq, que está recorriendo el IEnumerable de la misma manera que lo haría un bucle foreach. Sin embargo, en su código, MUCHO sucede bajo el capó. El foreach es probablemente mucho más eficiente si tu intención es escribir un ciclo de alto rendimiento. Linq está diseñado para un propósito diferente (acceso generalizado a datos).

La interfaz IEnumerable expone un método de iterador, que luego se llama continuamente por una construcción de bucle, como una consulta foreach o Linq. El iterador devuelve el siguiente elemento de la colección cada vez que se invoca.

+0

Por 'for' loop quise decir' for' y 'foreach' ... Edité la pregunta para reflejar que ... –

+0

Jonas, He actualizado mi respuesta para que refleje sus comentarios. –

+0

Bien, pero digamos que en lugar de hacer algo simple como el ejemplo anterior, tengo un escenario que es más como esto: Tengo una lista y quiero filtrar todo Person.LastName que coincida con una cierta expresión regular. Uso la sintaxis de Comprensión de lista para iterar sobre la lista y paso la expresión regular como una expresión lambda. ¿Sería eso más eficiente que escribir un bucle? –

4

LINQ funciona de manera diferente para diferentes tipos de datos. Usted está alimentando objetos, por lo que está utilizando LINQ-to-objects. Eso se traduce en un código similar a un for-loop directo.

Pero LINQ admite diferentes tipos de datos.Por ejemplo, si tuviera una tabla db llamada 'números', LINQ-to-SQL traduciría la misma consulta;

var evens = from num in numbers where num % 2 == 0 select num; 

en SQL como este;

select num from numbers where num % 2 = 0 

y luego lo ejecuta. Tenga en cuenta que no hace el filtrado creando un for-loop con un bloque 'if' dentro; la cláusula 'where' modifica la consulta enviada al servidor de la base de datos. La traducción de la consulta al código ejecutado es específica del tipo de datos que le está suministrando.

Así que no, LINQ no es solo azúcar sintáctica para for-loops, sino un sistema mucho más complicado para 'compilar' consultas. Para obtener más información, busque Linq Provider. Estos son los componentes como linq-to-objects y linq-to-xml, y usted puede escribir el suyo si se siente valiente.

Cuestiones relacionadas