2008-11-25 8 views
26

Cuando alguna vez pienso que puedo usar la palabra clave yield, doy un paso atrás y veo cómo impactará en mi proyecto. Siempre termino devolviendo una colección en lugar de yeilding porque siento que la sobrecarga de mantener el estado del método yeilding no me compra mucho. En casi todos los casos en los que devuelvo una colección, siento que el 90% del tiempo, el método de llamada se repetirá sobre todos los elementos de la colección, o buscará una serie de elementos en toda la colección.¿El rendimiento es útil fuera de LINQ?

entiendo su utilidad en LINQ, pero siento que sólo el equipo de LINQ está escribiendo tales queriable complejo de objetos que el rendimiento es útil.

Alguien ha escrito nada similar o no como LINQ donde el rendimiento fue útil?

+0

Quizás quiso decir fuera de LINQ, o IEnumerable? Me imagino que los usos del rendimiento que no sean los enumeradores serían bastante raros (e interesantes). Jon Skeet menciona uno en su libro ... – Benjol

+0

Aún soy escéptico. – Greg

+0

el uso muy interesante de los rendimientos se encuentra en Jeffrey Richter [Power Threading Library] (http://msdn.microsoft.com/en-us/magazine/cc546608.aspx) – Yurec

Respuesta

12

Recientemente tuve que hacer una representación de expresiones matemáticas en forma de una clase de Expresión. Al evaluar la expresión, tengo que atravesar la estructura del árbol con una caminata arbórea posterior a la orden. Para lograr esto he implementado IEnumerable <T> así:

public IEnumerator<Expression<T>> GetEnumerator() 
{ 
    if (IsLeaf) 
    { 
     yield return this; 
    } 
    else 
    { 
     foreach (Expression<T> expr in LeftExpression) 
     { 
      yield return expr; 
     } 
     foreach (Expression<T> expr in RightExpression) 
     { 
      yield return expr; 
     } 
     yield return this; 
    } 
} 

Entonces puede simplemente utilizar un foreach para recorrer la expresión.También puede agregar una propiedad para cambiar el algoritmo transversal según sea necesario.

+1

C# realmente necesita una palabra clave yieldcollection para abstraer el foreach (x en la colección) {yield x} bucles que todos escriben 100 veces por día en estos días :-( –

+3

si solo estás haciendo foreach (x en la colección) {return return x;} ... puede hacer .Seleccione (x => x). Si desea trabajar contra un conjunto de elementos en una colección, puede hacer un método de extensión .Foreach (IEnumerable col, Acción acción) –

27

Tenga en cuenta que con el rendimiento, que está interactuando sobre la colección una vez, pero cuando se genera una lista, podrás iterar sobre ella dos veces.

Tomemos, por ejemplo, un iterador de filtro:

IEnumerator<T> Filter(this IEnumerator<T> coll, Func<T, bool> func) 
{ 
    foreach(T t in coll) 
     if (func(t)) yield return t; 
} 

Ahora, se pueden encadenar esto:

MyColl.Filter(x=> x.id > 100).Filter(x => x.val < 200).Filter (etc) 

método Usted sería la creación (y lanzamiento) tres listas. Mi método lo repite solo una vez.

Además, cuando regrese a una colección, que están obligando a una implementación particular de ustedes los usuarios. Un iterador es más genérico.

+0

No debería leer: if (func (t))) rendimiento rendimiento t; –

+2

Ya'know ... Sin Intellisense, simplemente no puedo programar ..... –

+2

:) Tengo un proyecto VS2008 que no contiene nada más que fragmentos de código SO ... – GalacticCowboy

1

Personalmente, no he encontrado que estoy usando en mi rendimiento normal de programación del día a día. Sin embargo, recientemente comencé a jugar con los ejemplos de Robotics Studio y descubrí que el rendimiento se usa extensamente allí, por lo que también veo que se usa junto con el CCR (tiempo de concurrencia y coordinación) donde tienes problemas de asincronía y concurrencia.

De todos modos, todavía estoy tratando de entenderlo también.

1

El rendimiento es útil porque le ahorra espacio. La mayoría de las optimizaciones en programación hacen una compensación entre el espacio (disco, memoria, red) y el procesamiento. El rendimiento como una construcción de programación le permite iterar sobre una colección muchas veces en secuencia sin necesidad de una copia separada de la colección para cada iteración.

Considere este ejemplo:

static IEnumerable<Person> GetAllPeople() 
{ 
    return new List<Person>() 
    { 
     new Person() { Name = "George", Surname = "Bush", City = "Washington" }, 
     new Person() { Name = "Abraham", Surname = "Lincoln", City = "Washington" }, 
     new Person() { Name = "Joe", Surname = "Average", City = "New York" } 
    }; 
} 

static IEnumerable<Person> GetPeopleFrom(this IEnumerable<Person> people, string where) 
{ 
    foreach (var person in people) 
    { 
     if (person.City == where) yield return person; 
    } 
    yield break; 
} 

static IEnumerable<Person> GetPeopleWithInitial(this IEnumerable<Person> people, string initial) 
{ 
    foreach (var person in people) 
    { 
     if (person.Name.StartsWith(initial)) yield return person; 
    } 
    yield break; 
} 

static void Main(string[] args) 
{ 
    var people = GetAllPeople(); 
    foreach (var p in people.GetPeopleFrom("Washington")) 
    { 
     // do something with washingtonites 
    } 

    foreach (var p in people.GetPeopleWithInitial("G")) 
    { 
     // do something with people with initial G 
    } 

    foreach (var p in people.GetPeopleWithInitial("P").GetPeopleFrom("New York")) 
    { 
     // etc 
    } 
} 

(Es obvio que no es necesario que utilice el rendimiento con los métodos de extensión, sólo se crea un poderoso paradigma para pensar acerca de los datos.)

Como se puede ver, si tiene muchos de estos métodos de "filtro" (pero puede ser cualquier tipo de método que trabaje en una lista de personas) puede encadenar muchos de ellos sin requerir espacio de almacenamiento adicional para cada paso. Esta es una forma de aumentar el lenguaje de programación (C#) para expresar mejor sus soluciones.

El primer efecto secundario de rendimiento es que se aplaza la ejecución de la lógica de filtrado hasta que realmente necesite. Si, por lo tanto, crea una variable de tipo IEnumerable <> (con rendimientos) pero nunca itera a través de ella, nunca ejecutará la lógica ni consumirá el espacio, que es una optimización potente y gratuita.

El otro efecto secundario es que el rendimiento opera en la interfaz de recogida común más bajo (IEnumerable <>), que permite la creación de código de la biblioteca-como con una amplia aplicabilidad.

+0

Todos estos son * realmente * solo LINQ. Si está utilizando .NET 3.5, seguramente implementará GetPeopleWithInitial devolviendo personas. Donde (persona => persona.nombre.Iniciaciones (inicial)). –

+0

bien, sí y no. Lo que dices es cierto, pero tendrás que person => person.Name.Startswith() en todas partes. Con un método de biblioteca obtienes los beneficios obvios ... el rendimiento también viene en .NET 2 mientras que no todos tienen .NET 3.5 aún ... –

+0

Pieter: No digo que debas eliminar los métodos de la biblioteca, pero normalmente implementarlos con LINQ. Y cuando está tan cerca de ser LINQ, realmente no parece una respuesta a "cuando el rendimiento es útil fuera de LINQ": volver a implementar LINQ usted mismo no cuenta, IMO :) –

2

Siempre que su función devuelva IEnumerable debe usar "yielding". No en .Net> 3.0 solamente.

.Net 2.0 ejemplo:

public static class FuncUtils 
    { 
     public delegate T Func<T>(); 
     public delegate T Func<A0, T>(A0 arg0); 
     public delegate T Func<A0, A1, T>(A0 arg0, A1 arg1); 
     ... 

     public static IEnumerable<T> Filter<T>(IEnumerable<T> e, Func<T, bool> filterFunc) 
     { 
      foreach (T el in e) 
       if (filterFunc(el)) 
        yield return el; 
     } 


     public static IEnumerable<R> Map<T, R>(IEnumerable<T> e, Func<T, R> mapFunc) 
     { 
      foreach (T el in e) 
       yield return mapFunc(el); 
     } 
     ... 
11

En una empresa anterior, me encontré escribiendo bucles de esta manera:

for (DateTime date = schedule.StartDate; date <= schedule.EndDate; 
    date = date.AddDays(1)) 

Con un bloque iterador muy simple, yo era capaz de cambiar esto a :

foreach (DateTime date in schedule.DateRange) 

Hizo que el código fuera mucho más fácil de leer, IMO.

+2

Wow - ¡Código de Jon Skeet con el que no estoy de acuerdo! = X Desde el primer ejemplo, es obvio que estás iterando durante días, pero esa claridad falta en el segundo. Usaría algo como 'schedule.DateRange.Days()' para evitar la ambigüedad. –

+0

Eso requeriría algo más que la simple implementación de una sola propiedad, por supuesto. Diría que es obvio que un DateRange es un rango de fechas, que son días, pero es algo subjetivo. Podría haber sido llamado "Fechas" en lugar de DateRange - no estoy seguro. De cualquier forma, es menos peludo que el original. –

+0

Sí, es cierto. * se encoge de hombros * Personalmente, no me satisfaría, pero si está claro para el autor y para los futuros mantenedores, entonces realmente no importa. –

2

No estoy seguro de la implementación de rendimiento de C#(), pero en lenguajes dinámicos, es mucho más eficiente que crear toda la colección. en muchos casos, facilita trabajar con conjuntos de datos mucho más grandes que la RAM.

0

he utilizado yeild en las cosas de código no LINQ como este (asumiendo funciones no viven en una misma clase):

public IEnumerable<string> GetData() 
{ 
    foreach(String name in _someInternalDataCollection) 
    { 
     yield return name; 
    } 
} 

... 

public void DoSomething() 
{ 
    foreach(String value in GetData()) 
    { 
     //... Do something with value that doesn't modify _someInternalDataCollection 
    } 
} 

Hay que tener cuidado de no modificar inadvertidamente la colección que su GetData () la función se está iterando, o lanzará una excepción.

8

yield fue desarrollado para C# 2 (antes de Linq en C# 3).

Lo usamos en gran medida en una gran empresa de la aplicación web C# 2 cuando se trata de acceso a datos y cálculos muy repetidos.

Las colecciones son geniales cada vez que tiene algunos elementos que va a golpear varias veces.

Sin embargo, en muchos escenarios de acceso a datos, tiene una gran cantidad de elementos que no necesariamente tiene que pasar en una gran colección.

Esto es esencialmente lo que hace el SqlDataReader - es un enumerador personalizado solo hacia adelante.

Lo que yield le permite hacer es rápido y con un código mínimo, escriba sus propios enumeradores personalizados.

Todo yield se podría hacer en C# 1 - solo se necesita una gran cantidad de código para hacerlo.

Linq realmente maximiza el valor del comportamiento del rendimiento, pero ciertamente no es la única aplicación.

1

Tenga en cuenta que yield le permite hacer las cosas de manera "floja". Por perezoso, quiero decir que la evaluación del siguiente elemento en IEnumberable no se realiza hasta que el elemento realmente se solicite. Esto le permite el poder hacer un par de cosas diferentes. Una es que podría obtener una lista infinitamente larga sin la necesidad de hacer cálculos infinitos. En segundo lugar, puede devolver una enumeración de aplicaciones de función. Las funciones solo se aplicarán cuando itere por la lista.

0

El rendimiento es muy útil en general. Está en ruby, entre otros lenguajes, que admiten programación de estilo funcional, así que está ligado a linq. Es más al revés, que linq tiene un estilo funcional, por lo que usa el rendimiento.

Tuve un problema en el que mi programa usaba mucha CPU en algunas tareas en segundo plano. Lo que realmente quería era poder escribir funciones normales, para poder leerlas fácilmente (es decir, todo el argumento de subprocesamiento vs. evento). Y todavía ser capaz de romper las funciones si tomaron demasiada CPU. El rendimiento es perfecto para esto. Escribí un blog post sobre esto y la fuente está disponible para que todos puedan asimilar :)

18

entiendo su utilidad en LINQ, pero siento que sólo el equipo de LINQ está escribiendo tales queriable complejo de objetos que el rendimiento es útil.

El rendimiento fue útil tan pronto como se implementó en .NET 2.0, que fue mucho antes de que alguien pensara en LINQ.

¿Por qué debo escribir esta función:

IList<string> LoadStuff() { 
    var ret = new List<string>(); 
    foreach(var x in SomeExternalResource) 
    ret.Add(x); 
    return ret; 
} 

Cuando puedo usar el rendimiento y ahorrar el esfuerzo y la complejidad de crear una lista temporal sin una buena razón:

IEnumerable<string> LoadStuff() { 
    foreach(var x in SomeExternalResource) 
    yield return x; 
} 

También puede tiene enormes ventajas de rendimiento. Si su código solo usa los primeros 5 elementos de la colección, entonces usar el rendimiento a menudo evitará el esfuerzo de cargar algo más allá de ese punto. Si construyes una colección y luego la devuelves, pierdes una tonelada de tiempo y espacio cargando cosas que nunca necesitarás.

Podría seguir y seguir ....

+0

Creo que Anders Hejlsberg estaba trabajando en Linq hace varios años. –

2

Soy un gran fan de rendimiento en C#. Esto es especialmente cierto en los grandes frameworks locales donde a menudo los métodos o propiedades devuelven List que es un subconjunto de otro IEnumerable. Los beneficios que veo son:

  • el valor de retorno de un método que utiliza el rendimiento es inmutable
  • sólo está interactuando sobre la lista una vez
  • es una variable ejecución tardía o perezoso, es decir, el código de devolver los valores no se ejecutan hasta que sea necesario (aunque esto puede morder si no sabes lo que estás haciendo)
  • de los cambios de la lista de origen, usted no tiene que llamar para conseguir otro IEnumerable, que acaba de iterar sobre IEnumeable nuevo
  • muchos más

Otro beneficio ENORME del rendimiento es cuando su método potencialmente devolverá millones de valores. Tantos que existe el potencial de quedarse sin memoria simplemente construyendo la Lista antes de que el método pueda incluso devolverla.Con el rendimiento, el método puede simplemente crear y devolver millones de valores, y siempre que la persona que llama no almacene todos los valores. Por lo tanto, es bueno para operaciones de agregación/procesamiento de datos a gran escala

0

Las extensiones System.Linq IEnumerable son geniales, pero en algún momento desea más. Por ejemplo, considere la siguiente extensión:

public static class CollectionSampling 
{ 
    public static IEnumerable<T> Sample<T>(this IEnumerable<T> coll, int max) 
    { 
     var rand = new Random(); 
     using (var enumerator = coll.GetEnumerator()); 
     { 
      while (enumerator.MoveNext()) 
      { 
       yield return enumerator.Current; 
       int currentSample = rand.Next(max); 
       for (int i = 1; i <= currentSample; i++) 
        enumerator.MoveNext(); 
      } 
     } 
    }  
} 

Otra ventaja interesante de rendimiento es que la persona que llama no puede convertir el valor devuelto al tipo colección original y modificar su colección interna

Cuestiones relacionadas