martes, febrero 24, 2009

Rendimiento de la función deepCopy

Hace unas semanas os mostraba otra forma de clonar objetos en Java. Una de las dudas que me quedaban era el rendimiento (o performance que dirían algunos aun escribiendo en castellano) que tendría esta función en comparación al clone().
He escrito un programita para poder comparar las dos formas de clonar y sacar conclusiones, podéis ver el código al final del post pero primero os quiero comentar los resultados.
En un árbol de tres niveles (padre - hijos - nietos) con un sólo padre, 320 hijos y 112.000 nietos (350 hijos cada uno de los hijos), es decir, 112.321 objetos a clonar los resultados han sido:
  • DeepCopy: 2.219 segundos.
  • Clone: 0.234 segundos.

Y no sólo esto, sino que si aumentábamos el número de hijos y nietos, por ejemplo a 400 y 400 respectivamente, el clone() seguía funcionando sin problemas mientras el deepCopy() arrojaba una java.lang.OutOfMemoryError: Java heap space. Todo esto ejecutando el código desde Netbeans 6.5 y sin modificar el tamaño de la Heap.

Por el contrario si reducimos el número de objetos a clonar: 50 hijos y 300 nietos cada uno (15.051 objetos en total) los tiempos se aproximan, como es lógico, y la función deepCopy() no se comporta tan mal:

  • DeepCopy: 0.469 segundos.
  • Clone: 0.047 segundos.

Por tanto queda claro que el uso de la función deepCopy no es aconsejable. Y no debería usarse a no ser que no sea posible implementar el método clone().

Actualizado: Gimenete me ha comentado que la función clone() del ArrayList no hace una clonación de los objetos contenidos, esto es lo que se llama "shallow copy" (copia superficial). He tenido que implementar una función handClone que lo que haga sea un bucle del ArrayList llamando a los clone() de los objetos contenidos.

Aquí tenéis el código con el que hice las pruebas (como siempre os agradecería que me comentarais si he cometido algún error):


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
*
* @author Abel J.
*/
public class Main {

public static void main(String[] args) {

int NUMERO_DE_HIJOS = 520;
int NUMERO_DE_NIETOS = 350;

System.out.println("Comenzando");

PersonaDTO personaHijo;
PersonaDTO personaNieto;
List<PersonaDTO> listaPersonasHijos = new ArrayList<PersonaDTO>();
List<PersonaDTO> listaPersonasNietos = null;

for(int i=0; i<NUMERO_DE_HIJOS; i++){
listaPersonasNietos = new ArrayList<PersonaDTO>();
for(int j=0; j<NUMERO_DE_NIETOS; j++){
personaNieto = new PersonaDTO("Nieto"+j, j, "Calle del nieto "+j, null);
listaPersonasNietos.add(personaNieto);
}
personaHijo = new PersonaDTO("Hijo"+i, i, "Calle del hijo "+i, listaPersonasNietos);
listaPersonasHijos.add(personaHijo);
}

PersonaDTO persona = new PersonaDTO("Padre", 28, "Calle del padre", listaPersonasHijos);

System.out.println("Termina la carga de datos, ahora hacemos la copia:");
PersonaDTO personaCopiada = null;
long startTime1 = 0;
long endTime1 = 0;
long startTime2 = 0;
long endTime2 = 0;
try{
startTime1 = System.currentTimeMillis();
personaCopiada = (PersonaDTO)persona.deepCopy();
endTime1 = System.currentTimeMillis();

startTime2 = System.currentTimeMillis();
personaCopiada = (PersonaDTO)persona.clone();
endTime2 = System.currentTimeMillis();

double seconds1 = (endTime1 - startTime1) / 1000.0;
double seconds2 = (endTime2 - startTime2) / 1000.0;

System.out.println("DeepCopy: "+seconds1+" segundos.");
System.out.println("Clone: "+seconds2+" segundos.");
}catch(Exception e){
System.out.println("Exception mientras copiaba: "+ e.getMessage());
}
}
}

/**
*
* @author Abel J.
*/
class PersonaDTO implements Serializable, Cloneable{
private String nombre;
private int edad;
private String direccion;
private List<PersonaDTO> hijos;

public PersonaDTO(){}

public PersonaDTO(String nombre, int edad, String direccion, List<PersonaDTO> hijos) {
this.nombre = nombre;
this.edad = edad;
this.direccion = direccion;
this.hijos = hijos;
}

/**
* @return the nombre
*/
public String getNombre() {
return nombre;
}

/**
* @param nombre the nombre to set
*/
public void setNombre(String nombre) {
this.nombre = nombre;
}

/**
* @return the edad
*/
public int getEdad() {
return edad;
}

/**
* @param edad the edad to set
*/
public void setEdad(int edad) {
this.edad = edad;
}

/**
* @return the direccion
*/
public String getDireccion() {
return direccion;
}

/**
* @param direccion the direccion to set
*/
public void setDireccion(String direccion) {
this.direccion = direccion;
}

/**
* @return the hijos
*/
public List<PersonaDTO> getHijos() {
return hijos;
}

/**
* @param hijos the hijos to set
*/
public void setHijos(List<PersonaDTO> hijos) {
this.hijos = hijos;
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PersonaDTO other = (PersonaDTO) obj;
if ((this.nombre == null) ? (other.nombre != null) : !this.nombre.equals(other.nombre)) {
return false;
}
if (this.edad != other.edad) {
return false;
}
if (this.hijos != other.hijos && (this.hijos == null !this.hijos.equals(other.hijos))) {
return false;
}
return true;
}

@Override
public int hashCode() {
int hash = 7;
hash = 11 * hash + (this.nombre != null ? this.nombre.hashCode() : 0);
hash = 11 * hash + this.edad;
hash = 11 * hash + (this.direccion != null ? this.direccion.hashCode() : 0);
hash = 11 * hash + (this.hijos != null ? this.hijos.hashCode() : 0);
return hash;
}

public Object deepCopy() throws Exception {
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
// serialize and pass the object
oos.writeObject(this);
oos.flush();
ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bin);
// return the new object
return ois.readObject();
} catch (Exception ex) {
throw ex;
} finally {
try {
oos.close();
ois.close();
} catch (IOException ex) {
throw ex;
}
}
}

// clonacion a mano
static ArrayList<PersonaDTO> handClone(List<PersonaDTO> list) throws CloneNotSupportedException{
if(list != null){
ArrayList<PersonaDTO> newlist = new ArrayList<PersonaDTO>();
for(PersonaDTO obj : list){
newlist.add((PersonaDTO)(((PersonaDTO)obj).clone()));
}
return newlist;
}else{
return null;
}
}

@Override
protected Object clone() throws CloneNotSupportedException {
PersonaDTO obj = null;
try{
obj=(PersonaDTO)super.clone();
obj.setHijos(handClone(getHijos()));
}catch(CloneNotSupportedException ex){
System.out.println("No se puede duplicar");
}
return obj;
}
}

5 comentarios:

  1. No estoy seguro del todo pero creo que el clone() del ArrayList no clona el contenido. Entonces el deepCopy crea nuevos objetos para todo, mientras que usando el clone() de ArrayList lo único nuevo que creas es un nuevo PersonaDTO con un ArrayList nuevo, pero todos los hijos son los mismos. Los hijos no los has clonado.

    saludos!

    ResponderEliminar
  2. Sí, yo también creo que he metido la pata con eso, luego le echo un ojo porque es imposible que no tarde algo de tiempo.

    ResponderEliminar
  3. Ahora creo que ya está bien... al menos los tiempos son más creíbles.

    ResponderEliminar
  4. Alguien me puede ayudar con esto: Ocupo desarrollar un programa que rellena un arreglo con mil números enteros, se rellena con los números del 1 al 1000. Luego este mismo arreglo debe de ejecutar una copia de si mismo, pero le debo de enviar un parámetro para saber
    por cuál versión de si mismo “va”. Al final, la idea es medir cuántas versiones del mismo arreglo puede ejecutarse, sin caer en fallas de espacio de memoria.

    ResponderEliminar