#1101 closed doubt (fixed)
Excessive and strange RAM usage
Reported by: | Pedro Gea | Owned by: | Víctor de Buen Remiro |
---|---|---|---|
Priority: | highest | Milestone: | Mantainance |
Component: | Kernel | Version: | |
Severity: | blocker | Keywords: | |
Cc: |
Description
Intentando solucionar problemas debidos al excesivo consumo de memoria RAM me encuentro con una circunstancia que no soy capaz de explicarme.
Resulta que al utilizar dos funciones que aparentemente deberían funcionar igual se obtienen comportamientos distintos en el consumo de RAM. Esto también se aprecia en el incremento de NObjects.
Aunque merece la pena insistir que no se trata de un problema de perdida de memoria, sino de un aparente consumo excesivo de memoria.
- En primer lugar creamos un conjunto de matrices grandes para aprenciar bien los cambios en RAM.
Set matrices = For(1, 500, Matrix (Real i) { Rand(50000, 1, 0, 1) });
Si comprobamos el incremento de NObject obtenemos 2507:
Real no1 = Copy(NObject); Set matrices = For(1, 500, Matrix (Real i) { Rand(50000, 1, 0, 1) }); Real no2 = Copy(NObject); Real no2 - no1; //-> 2507
Mientras que si asignamos un nombre a las matrices recién creadas obtenemos 507:
Real no1 = Copy(NObject); Set matrices = For(1, 500, Matrix (Real i) { Matrix a = Rand(50000, 1, 0, 1) }); Real no2 = Copy(NObject); Real no2 - no1; //-> 507
Sin embargo esto no parece afectar al consumo de RAM siendo en ambos casos de unos 288 MB aproximadamente.
- En segundo lugar definimos dos funciones que devuelven la matriz pasada como argumento con el nombre "nombre":
Matrix Use1(Matrix object) { Matrix nombre = Copy(object) }; Matrix Use2(Matrix object) { Matrix nombre = object };
La primera asigna una copia del objeto, mientras que la segunda no hace la copia.
Aunque como son asignaciones por valor se espera que esto sea lo mismo.
Las consecuencias sin embargo no son para nada similares:
Si hacemos uso de Use1
(la que hace copia) tanto el valor de NObject como la RAM aumentan lo esperado. La RAM crece otros 288 MB hasta los 576 MB comparada con el comienzo.
Real no3 = Copy(NObject); Set matrices2 = EvalSet(matrices, Anything (Matrix m) { Matrix data = m; Use1(data) }); Real no4 = Copy(NObject); Real no4 - no3; //-> 505
Mientras que si hacemos uso de Use2
tanto el valor de NObject como la RAM aumentan excesivamente. La RAM crece 576 MB (en lugar de la mitad) llegando hasta los 864 MB comparada con el comienzo:
Real no3 = Copy(NObject); Set matrices2 = EvalSet(matrices, Anything (Matrix m) { Matrix data = m; Use2(data) }); Real no4 = Copy(NObject); Real no4 - no3; //-> 2505
El problema está en el consumo de RAM, y no en que aparezcan más o menos NObjects, aunque sin duda sería interesante comprender lo que está ocurriendo.
Attachments (1)
Change History (13)
comment:1 Changed 14 years ago by
Summary: | Excessive and strange RAM ussage → Excessive and strange RAM usage |
---|
comment:2 Changed 14 years ago by
Status: | new → accepted |
---|
comment:3 Changed 14 years ago by
Component: | Various → Kernel |
---|---|
Milestone: | → Mantainance |
comment:4 Changed 14 years ago by
La primera parte ya la entiendo, aunque no la esperaba. Entiendo que es un comportamiento propio de las funciones compiladas y de las funciones que en general no devuelven como resultado una variable con nombre.
Así el comportamiento de NObject al usar myRand1
o myRand2
en lugar de Rand
es distinto: se almacena un árbol de objetos aún mayor en el primer caso, y simplemente la matriz en el segundo.
Matrix myRand1(Real row, Real col, Real min, Real max) { Rand(row, col, min, max) }; Matrix myRand2(Real r, Real c, Real mi, Real ma) { Matrix return = Rand(row, col, min, max) };
No sé si es razonable, pero me veo tentado a devolver siempre los resultados con nombre (return
) en todas las funciones y métodos que programe. Ya que no le veo ninguna utilidad al almacenamiento del árbol de objetos y tiendo a relacionarlo (con acierto o no) con lo que ocurre en la segunda parte.
Si al usar Use2
nombro la matriz retornada, el problema desaparece:
Real no3 = Copy(NObject); Set matrices2 = EvalSet(matrices, Anything (Matrix m) { Matrix data = m; Matrix return = Use2(data) }); Real no4 = Copy(NObject); Real no4 - no3; //-> 505
¿Es razonable que me plantee revisar todos mis funciones y métodos y en general la manera de programar las salidas de las funciones (Code
)?
Mi preocupación ya no es tanto que este problema se solucione o no, sino comprender por qué ocurre y cómo evitarlo para poder asegurar la eficiencia y calidad del código que programamos.
comment:5 Changed 14 years ago by
Yo quiero revisar este tema con un poco de tranquilidad, a ver si veo cómo hacer que siempre se comporte igual.
El motivo detrás de todo esto está en el tratamiento de los tipos abstractos como series y conjuntos temporales infinitos que necesitan todo su árbol para poder evaluarse pues no se pueden resumir en un conjunto finito de datos.
Creo que podría intervenir en el evaluador de funciones cuando el tipo de datos lo permita y reducir el objeto devuelto a su contenido eliminando el objeto temporal con el árbol semántico.
El problema es que eso requiere un nivel de chequeo importante, no sólo hay que pasar los tests sino ver cómo afecta a la velocidad y a la memoria, y comprobar que no hay efectos secundarios inesperados.
Cuando la tenga, ¿podría pasarte una versión binaria de pruebas para que comprobaras también por tu cuenta si mejora el rendimiento en el uso de MMS?
comment:6 Changed 14 years ago by
Añado un nuevo tipo de extraños comportamientos en la RAM que quizá probablemente estén relacionados con los anteriores.
Adjunto un archivo con el código.
El test está construido siguiendo una línea similar a la que se usó al enunciar este tique: se comprueba el número de NObjects y el uso de RAM al hacer una misma cosa de varias formas distintas.
En el código que se adjunta:
- se crean unas matrices grandes (los datos) que nos permiten comprobar claramente los cambios en RAM,
- se crean unas instancias (unos nameblocks) con un atributo data de tipo
Set
conteniendo a una de estas matrices, - se liberan los datos, manteniendo sólo las instancias.
Se usan tres formas (según el método Instance1
, Instance2
o Instance3
):
- En el primero se construye la instancia de un modo que se podría denominar normal, obteniéndose un numero alto de NObjects y una RAM que no se deja liberar.
- En el segundo se construye la instancia vacía (sin datos) y luego se añaden. En este caso los NObjects se reducen y la RAM se libera.
- Se devuelve una copia de la instancia creada, en lugar de ella misma, reduciéndose aún más el número de NObjects y liberándose la RAM.
La duda, lo habitual, ¿qué está ocurriendo?
P.S. Sin problemas, respecto a lo de usar una versión de pruebas para comprobar mejoras de rendimiento. Escríbeme un mail para lo que sea.
Changed 14 years ago by
Attachment: | TestInstances.tol added |
---|
comment:7 Changed 14 years ago by
Creo que esto último se debe a que DeepCopy no es un operador especial como Copy sino una función normal definida con las macros DeclareContensClass y DefExtOpr que no tiene ningún control sobre el objeto devuelto, sino sobre su contenido.
Voy a probar a reecribirla como función especial a ver si se soluciona el problema y ya te paso la versión de pruebas.
comment:8 Changed 14 years ago by
Resolution: | → fixed |
---|---|
Status: | accepted → closed |
La primera parte es clara: si no les pones nombre se guarda todo el árbol de objetos.
En la expresión
Rand(50000, 1, 0, 1)
hay 4 argumentos más el resultado son 5, por 500 iteraciones dan 2500 objetos de los que 2000 no ocupan casi nada en comparación con las matrices. Los otros 7 son los de las expresiones creadas entre medio, la del ciclo y los contadores de objetos y demás.Sobre el segundo punto parece quedarse una copia temporal de más. Luego se borrará por lo que a la larga no importa mucho, salvo que se trate de objetos muy grandes como es el caso. Lo miraré a ver si encuentro algo corregible, aunque ya adelanto que es el tipo de cosas complicadas, que arreglas una y fastidias cuatro.