Si funciona no lo toques... cuantas veces habré oido ese "refrán", y cuantas veces lo he odiado. Con este tipo de filosofías nunca se evoluciona ni mejora.
¿Por que digo esto? Porque a raiz de las dos entradas enteriores, en las que he estado probando el rendimiento de diferentes métodos de clonado (todo con Visual Studio 2012 y .NET 4.5) me dije: ¿Y que pasará con otras versiones del Framework (anteriores obviamente)?
Pues bien... me puse a ello, y bombazo final! Esta vez Microsoft cumplió lo que anunciaba: La ultima versión del Framework rinde más... mirad:
¡La mejora cuando clonamos gran cantidad de objetos es espectacular!
Por ejemplo, clonando 50000 objetos con reflection pasamos de alrededor de 15 segundos a 9, si comparamos .NET 3.5 y .NET 4.5. ¡Guau!
¿A que se puede deber?
Sospecho que el principal motivo puede ser la mejora de la reserva de memoria para objetos grandes (mi ejemplo no deja de ser el clonado de un objeto grande, un garaje con hasta 50000 coches jeje)
Aqui hablan de esto además de otros cambios en el Framework 4.5 relacionados con el rendimiento: http://msdn.microsoft.com/en-us/magazine/hh882452.aspx
Asi que ya sabeis... si soys de los de "Si funciona no lo toques" os perdereis las mejoras que suelen traer las nuevas versiones... esta vez ha sido en rendimiento, otras será en seguridad, productividad, etc...
Me apunto seguir comparando versiones, con otros casos, como consultas Linq To Objects sobre colecciones grandes por ejemplo. De momento esto ha sido todo
Mostrando entradas con la etiqueta Performance. Mostrar todas las entradas
Mostrando entradas con la etiqueta Performance. Mostrar todas las entradas
viernes, 8 de febrero de 2013
lunes, 21 de enero de 2013
Clone Wars (o el arte de clonar objetos). Parte 2
Con algo de retraso continuo analizando métodos para clonar objetos, esta vez rebuscando por la red, en busca de algo diferente a lo habitualmente usado que se expuse en la primera entrada (http://serginet.blogspot.com.es/#!/2012/12/clone-wars-o-el-arte-de-clonar-objetos.html).
Para empezar, clonado mediante código intermedio, es de ir IL. Parece rebuscado, pero promete buen rendimiento. Su autor (Whizzo) da detalles aquí: http://whizzodev.blogspot.com.es/2008/03/object-cloning-using-il-in-c.html
Una prueba rápida arroja excelentes resultados... peeero, el método no vale para hacer un deep cloning de colecciones (una lista de objetos dentro de tu objeto). El autor aún no lo haimplementado, y va pasando el tiempo :-(. Quizás algún día me ponga a ello, pero por ahora vamos a descartar este método.
Si, podemos encontrar otros serializadores además de los que vienen con .NET Framework. Por ejemplo, neocognitron nos propone su AltSerializar, que hace una serialización binaria en principio más eficiente que la ofrecida de serie por Microsoft.
Su uso es sencillo, basta con referenciar el assembly, y serializar del siguiente modo:
¿Que tal el rendimiento?
Pues aquí lo podéis ver:
He utilizado el mismo método que en la primera parte entrada de Clone Wars: Clonamos 1000,10000, 25000 y 50000 objetos tres veces, sacando la media de tiempos.
Como veis, tenemos los tipos de clonado de la primera parte del artículo (Reflection y Serializacion), el clonado con serialización alternativa, y además he añadido el clonado manual (¡Ya sabíamos que iba a ser el más rapido!)
Como veis parece que este serializador alternativo es rápido, muy rápido, incluso más que la serialización con reflection. ¡Buen candidato!
Además hay que tener en cuenta que se le pueden "tocar las tripas"... seguro que podríamos mejorar.
Una vez visto esto, modifico las conclusiones que saqué en la primera parte del artículo para decir: Implementa un clonado manual siempre que puedas, es decir, siempre que tengas pocas clases, o en caso contrario suficiente tiempo para hacerlo con seguridad, es decir, con pruebas unitarias que aseguren que no "metes la pata".
Si esto anterior no puede ser, opta por el clonado con el serializador alternativo que hemos visto aquí.
Aqui os dejo el código fuente, en el que incluyo también el clonado IL. Ya comenté que no clona colecciones, quizas alguien se anima!
http://sdrv.ms/SrTa4F
Continuare en breve (me pongo a escribir ahora mismo) con la parte 3 del artículo: Clone Wars: El Bombazo Final...
Clonado IL
Para empezar, clonado mediante código intermedio, es de ir IL. Parece rebuscado, pero promete buen rendimiento. Su autor (Whizzo) da detalles aquí: http://whizzodev.blogspot.com.es/2008/03/object-cloning-using-il-in-c.html
Una prueba rápida arroja excelentes resultados... peeero, el método no vale para hacer un deep cloning de colecciones (una lista de objetos dentro de tu objeto). El autor aún no lo haimplementado, y va pasando el tiempo :-(. Quizás algún día me ponga a ello, pero por ahora vamos a descartar este método.
Clonado con serialización binaria alternativa
Si, podemos encontrar otros serializadores además de los que vienen con .NET Framework. Por ejemplo, neocognitron nos propone su AltSerializar, que hace una serialización binaria en principio más eficiente que la ofrecida de serie por Microsoft.
Su uso es sencillo, basta con referenciar el assembly, y serializar del siguiente modo:
1: using System.Text;
2:
3: byte[] bytes = Serializer.Serialize(source);
4: var res= (T)Serializer.Deserialize(bytes);
5: TraceTime();
6: return res;
¿Que tal el rendimiento?
Pues aquí lo podéis ver:
He utilizado el mismo método que en la primera parte entrada de Clone Wars: Clonamos 1000,10000, 25000 y 50000 objetos tres veces, sacando la media de tiempos.
Como veis, tenemos los tipos de clonado de la primera parte del artículo (Reflection y Serializacion), el clonado con serialización alternativa, y además he añadido el clonado manual (¡Ya sabíamos que iba a ser el más rapido!)
Como veis parece que este serializador alternativo es rápido, muy rápido, incluso más que la serialización con reflection. ¡Buen candidato!
Además hay que tener en cuenta que se le pueden "tocar las tripas"... seguro que podríamos mejorar.
Conclusiones
Una vez visto esto, modifico las conclusiones que saqué en la primera parte del artículo para decir: Implementa un clonado manual siempre que puedas, es decir, siempre que tengas pocas clases, o en caso contrario suficiente tiempo para hacerlo con seguridad, es decir, con pruebas unitarias que aseguren que no "metes la pata".
Si esto anterior no puede ser, opta por el clonado con el serializador alternativo que hemos visto aquí.
Aqui os dejo el código fuente, en el que incluyo también el clonado IL. Ya comenté que no clona colecciones, quizas alguien se anima!
http://sdrv.ms/SrTa4F
Continuare en breve (me pongo a escribir ahora mismo) con la parte 3 del artículo: Clone Wars: El Bombazo Final...
sábado, 29 de diciembre de 2012
Clone Wars (o el arte de clonar objetos). Parte 1
Empiezo con mi primer artículo, un tema recurrente cuando estamos desarrollando: ¿Como clonamos nuestros objetos?
Antes que nada aclarar, que en tipos por referencia (clases por ejemplo), no basta con igualar, ya que estaríamos copiando la referencia.
Si hiciesemos:
var coche=new Car() {Marca="Opel", Modelo="Astra"};
var coche2=coche;
coche.Marca="Seat";
¿Que marca tendríamos en coche2? (redoble de tambores)
Pues Seat... porque hemos copiado las referencias (punteros para los viejos rockeros que empezamos con C++ )
Aqui es donde entra el clonado, y por clonar, me refiero a una copia bit a bit... una copia exacta de nuestros objetos.
Tenemos 2 opciones, clonar de un modo manual, o dejar que mecanismos automáticos lo hagan por nosotros:
¿Os suena la interfaz ICloneable? Se trata de que la implementemos
Y claro... ahora tocaría implementar el clonado para la clase Engine... y así hasta el infinito.
Parece evidente que un clonado manual va a ser el más efectivo (desde un punto de vista del rendimiento), pero no es oro todo lo que reluce, tiene 2 grandes problemas:
Existen métodos para realizar los clonados de un modo automatico, y así evitarnos la implementación del método Clone() para cada uno de nuestros tipos.
Los más comunes son el clonado mediante serialización y el clonado mediante reflection. Hay otros métodos más sofisticados... esos los dejo para un segundo post sobre este tema.
Sencillo y suele funcionar bien. ¿Por que digo suele? Porque dependiendo del tipo de serialización que usemos (binaria, XML o de contrato), algunos miembros privados podrían no clonarse bien. Por otro lado, a priori parece que no será muy bueno. Esto lo veremos más adelante.
¿Cual es su ventaja? Bajo mi punto de vista la simplicidad. supongo que estareis de acuerdo cuando veais el siguiente tipo de clonado, que usa el API Reflection de .NET.
Vamos cuanto antes al código, solo un apunte: Queremos lo que se llama un clonado en profundidad, es decir si nuestro objeto tiene dentro un atributo de un tipo por referencia, debemos clonarlo también, no copiar la referencia. En el tipo de clonado anterior (serialización) no tuvimos que tenerlo en cuenta, ya que el serializador nos abstrae de esto, aqui no podemos obviarlo, así que he realizado una implementación recursiva:
Se trata solo de un ejemplo, no esta completo, ya que solo clono propiedades, y ademas solo trato colecciones que implementen IList. Afortunadamente rebuscando un poco pueden encontrarse implementaciones completas.
El algoritmo es sencillo:
- Si es un tipo que implementa ICloneable, lo clonamos (estamos de suerte!)
- Si es un tipo por valor, podemos copiarlo sin más (tipos básicos)
- En caso contrario, será un tipo por referencia, tenemos que clonarlo (llamada recursiva)
He clonado una serie de objetos con los 2 métodos: un garage, que tienes varios vehículos, 1000 , 10000, 25000 y finalmente 50000. Estos son los resultados:
Nota: Se han realizado varias pruebas consecutivas y se han tomado tiempos medios.
Efectivamente, las cosas empiezan igualadas, pero según aumenta el número de objetos a clonar, reflection empieza a marcar diferencias.
Aqui os dejo todo el código: los clonadores, las clases a clonar y las mediciones de tiempo:
http://sdrv.ms/10zGTin
Continuará...
Antes que nada aclarar, que en tipos por referencia (clases por ejemplo), no basta con igualar, ya que estaríamos copiando la referencia.
Si hiciesemos:
var coche=new Car() {Marca="Opel", Modelo="Astra"};
var coche2=coche;
coche.Marca="Seat";
¿Que marca tendríamos en coche2? (redoble de tambores)
Pues Seat... porque hemos copiado las referencias (punteros para los viejos rockeros que empezamos con C++ )
Aqui es donde entra el clonado, y por clonar, me refiero a una copia bit a bit... una copia exacta de nuestros objetos.
Tenemos 2 opciones, clonar de un modo manual, o dejar que mecanismos automáticos lo hagan por nosotros:
Clonado manual:
¿Os suena la interfaz ICloneable? Se trata de que la implementemos
1: public class Car: ICloneable{
2:
3: public string Marca;
4:
5: public Engine MainEngine;
6:
7: public object Clone()
8:
9: {
10:
11: Car c = new Car ();
12:
13: c.Marca= this.Marca;
14:
15: if (this.MainEngine!= null)
16:
17: c.MainEngine= (Engine )this.MainEngine.Clone();
18:
19: return c;
20:
21: }
22:
23: }
Y claro... ahora tocaría implementar el clonado para la clase Engine... y así hasta el infinito.
Parece evidente que un clonado manual va a ser el más efectivo (desde un punto de vista del rendimiento), pero no es oro todo lo que reluce, tiene 2 grandes problemas:
- Es tedioso (imagina implementar el clonado para decenas o centenares de tipos)
- Es muy propenso a errores... y creedme, estos errores son de los difíciles de encontrar
Clonado automático:
Existen métodos para realizar los clonados de un modo automatico, y así evitarnos la implementación del método Clone() para cada uno de nuestros tipos.
Los más comunes son el clonado mediante serialización y el clonado mediante reflection. Hay otros métodos más sofisticados... esos los dejo para un segundo post sobre este tema.
1. Clonado mediante serialización
Este método es realmente sencillo, se trata de serializar y deserializar el objeto a clonar, con lo que obtenemos una copia exacta. Sería algo así:1: public override T Clone<T>(T source)
2: {
3: StartTime();
4:
5: if (!typeof(T).IsSerializable)
6: {
7: throw new ArgumentException("The type must be serializable.", "source");
8: }
9:
10: // Don't serialize a null object, simply return the default for that object
11: if (Object.ReferenceEquals(source, null))
12: {
13: return default(T);
14: }
15:
16: IFormatter formatter = new BinaryFormatter();
17:
18: using (Stream stream = new MemoryStream())
19: {
20: formatter.Serialize(stream, source);
21: stream.Seek(0, SeekOrigin.Begin);
22:
23: var result= (T)formatter.Deserialize(stream);
24: TraceTime();
25: return result;
26: }
27: }
Sencillo y suele funcionar bien. ¿Por que digo suele? Porque dependiendo del tipo de serialización que usemos (binaria, XML o de contrato), algunos miembros privados podrían no clonarse bien. Por otro lado, a priori parece que no será muy bueno. Esto lo veremos más adelante.
¿Cual es su ventaja? Bajo mi punto de vista la simplicidad. supongo que estareis de acuerdo cuando veais el siguiente tipo de clonado, que usa el API Reflection de .NET.
2. Clonado con Reflection
Vamos cuanto antes al código, solo un apunte: Queremos lo que se llama un clonado en profundidad, es decir si nuestro objeto tiene dentro un atributo de un tipo por referencia, debemos clonarlo también, no copiar la referencia. En el tipo de clonado anterior (serialización) no tuvimos que tenerlo en cuenta, ya que el serializador nos abstrae de esto, aqui no podemos obviarlo, así que he realizado una implementación recursiva:
1: protected T CloneRec<T>(T source)
2: {
3: if (Object.ReferenceEquals(source, null))
4: {
5: return default(T);
6: }
7: var destination = Activator.CreateInstance(source.GetType());
8: var props = destination.GetType().GetProperties();
9: int propIndex = 0;
10:
11: foreach (var propInfo in props)
12: {
13: if (propInfo.GetValue(source,null) != null)
14: {
15:
16: if (ImplementsInterface(propInfo.PropertyType, "IList"))
17: {
18: var enumerable = (IEnumerable)propInfo.GetValue(source,null);
19: //int i = 0;
20: IList list = (IList)propInfo.GetValue(destination,null);
21: foreach (var item in enumerable)
22: list.Add(CloneRec(item));
23: }
24: else if (ImplementsInterface(propInfo.PropertyType, "ICloneable"))
25: {
26: props[propIndex].SetValue(destination, ((ICloneable)propInfo.GetValue(source, null)).Clone(), null);
27: }
28: else if(propInfo.PropertyType.IsValueType)
29: {
30: props[propIndex].SetValue(destination, propInfo.GetValue(source, null), null);
31: }
32: else
33: {
34: props[propIndex].SetValue(destination, CloneRec(propInfo.GetValue(source, null)),null);
35: }
36: }
37: propIndex++;
38: }
39: return (T)destination;
40: }
Se trata solo de un ejemplo, no esta completo, ya que solo clono propiedades, y ademas solo trato colecciones que implementen IList. Afortunadamente rebuscando un poco pueden encontrarse implementaciones completas.
El algoritmo es sencillo:
- Creamos el objeto destino
- Leo mediante relection todas las propiedades
- Para cada propiedad
- Si es un tipo que implementa ICloneable, lo clonamos (estamos de suerte!)
- Si es un tipo por valor, podemos copiarlo sin más (tipos básicos)
- En caso contrario, será un tipo por referencia, tenemos que clonarlo (llamada recursiva)
Rendimiento:
No vamos a encontrar muchas sorpresas: el clonado manual es sin duda el más rápido. En cuanto al automático, se suele decir que reflection rinde un poco mejor que el serializado. ¿Será cierto? Vamos a comprobarlo.He clonado una serie de objetos con los 2 métodos: un garage, que tienes varios vehículos, 1000 , 10000, 25000 y finalmente 50000. Estos son los resultados:
Nota: Se han realizado varias pruebas consecutivas y se han tomado tiempos medios.
Efectivamente, las cosas empiezan igualadas, pero según aumenta el número de objetos a clonar, reflection empieza a marcar diferencias.
Conclusiones:
Hay varias opciones, y escribiré una segunda entrada con algunas más, mientras tanto, yo me decantaría por lo siguiente:- Clonado manual siempre que podamos
- Clonado basado en serialización si vamos a clonar pocos objetos
- Clonado basado en reflection para el resto de casos
Aqui os dejo todo el código: los clonadores, las clases a clonar y las mediciones de tiempo:
http://sdrv.ms/10zGTin
Continuará...
Suscribirse a:
Entradas (Atom)




