c# - tutorial - ¿El rendimiento es útil fuera de LINQ?
sintaxis linq c# (14)
Cuando alguna vez creo que puedo usar la palabra clave yield, doy un paso atrás y veo cómo impactará 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.
Entiendo su utilidad en linq, pero creo que solo el equipo de linq está escribiendo objetos queriables tan complejos que el rendimiento es útil.
¿Alguien ha escrito algo así como o no como linq donde el rendimiento fue útil?
El rendimiento es útil porque te 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.
considera 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
}
}
(Obviamente, no es necesario utilizar el rendimiento con los métodos de extensión, solo crea un poderoso paradigma para pensar en los datos).
Como 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 del rendimiento es que retrasa la ejecución de la lógica de filtrado hasta que realmente lo requiera. Si, por lo tanto, crea una variable de tipo IEnumerable <> (con yields) 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 recopilación común más baja (IEnumerable <>) que permite la creación de código tipo biblioteca con amplia aplicabilidad.
He usado yeild en código no lineal cosas como esta (suponiendo que las funciones no viven en la 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
}
}
Sin embargo, debe tener cuidado de no modificar inadvertidamente la colección sobre la que se está iterando su función GetData () o generará una excepción.
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.
Personalmente, no he encontrado que estoy usando rendimiento en mi programación diaria normal. 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.
Recientemente tuve que hacer una representación de expresiones matemáticas en la 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, implementé IEnumerable <T> de esta manera:
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, simplemente puedo usar un foreach para recorrer la expresión. También puede agregar una propiedad para cambiar el algoritmo transversal según sea necesario.
Siempre que tu función regrese a IEnumerable, deberías usar "yielding". No en .Net> 3.0 solamente.
Ejemplo de .Net 2.0:
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);
}
...
Tenga en cuenta que con el rendimiento, está iterando sobre la colección una vez, pero cuando crea una lista, estará iterando sobre ella dos veces.
Tome, 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, puedes encadenar esto:
MyColl.Filter(x=> x.id > 100).Filter(x => x.val < 200).Filter (etc)
Tu método sería crear (y lanzar) tres listas. Mi método lo repite solo una vez.
Además, cuando devuelve una colección, está forzando una implementación particular en sus usuarios. Un iterador es más genérico.
Tenga en cuenta que el rendimiento 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.
yield
fue desarrollado para C # 2 (antes de Linq en C # 3).
Lo usamos en gran medida en una aplicación web de gran empresa C # 2 cuando se trataba de acceso a datos y cálculos muy repetidos.
Las colecciones son excelentes 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 debe pasar en una gran colección.
Esto es esencialmente lo que hace el SqlDataReader
: es un enumerador personalizado solo hacia adelante.
Lo que le permite hacer es rápidamente y con un código mínimo escribir sus propios enumeradores personalizados.
Todo lo que se puede hacer se puede hacer en C # 1, solo se necesita una gran cantidad de código para hacerlo.
Linq realmente maximiza el valor del comportamiento de rendimiento, pero ciertamente no es la única aplicación.
Entiendo su utilidad en linq, pero creo que solo el equipo de linq está escribiendo objetos queriables tan complejos 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é debería 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 tener grandes 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 ....
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 donde 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 basado en el enhebrado vs. el evento). Y todavía ser capaz de romper las funciones si tomaron demasiada CPU. El rendimiento es perfecto para esto. Escribí una publicación en el blog sobre esto y la fuente está disponible para todos grok :)
Soy un gran fan de Yield 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 usa el rendimiento es inmutable
- solo estás iterando sobre la lista una vez
- es una variable de ejecución tardía o diferida, lo que significa que el código para devolver los valores no se ejecuta hasta que se necesite (aunque esto puede morderte si no sabes lo que estás haciendo)
- de los cambios en la lista fuente, no tienes que llamar para obtener otro IEnumerable, simplemente iteras sobre IEnumeable nuevamente
- mucho mas
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 el procesamiento de datos a gran escala / operaciones de agregación
Las extensiones de System.Linq IEnumerable son geniales, pero a veces quieres 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 ceder es que la persona que llama no puede convertir el valor de retorno al tipo de colección original y modificar su colección interna
En una empresa anterior, me encontré escribiendo loops como este:
for (DateTime date = schedule.StartDate; date <= schedule.EndDate;
date = date.AddDays(1))
Con un bloque iterador muy simple, pude cambiar esto a:
foreach (DateTime date in schedule.DateRange)
Hizo que el código sea mucho más fácil de leer, IMO.