uqbar-project / wollok

Wollok Programming Language
GNU General Public License v3.0
60 stars 16 forks source link

up, right, left y down de las posiciones trae problemas de performance #1941

Closed JuanFdS closed 3 years ago

JuanFdS commented 4 years ago

O al menos eso parece, estaba teniendo problemas en un juego de wollok game que estaba armando y decidí reemplazar un lugar donde estaba usando up y right por un game.at (calculando las posiciones y pasandoselas) y eso mejoro muchisimo la performance.

Creé un ejemplo mínimo que al menos en mi compu se puede notar, pero no tengo idea que puede ser realmente el problema

https://github.com/uqbar-project/wollok-game-ejemplo-cambios-de-posicion-bajan-la-performance

leandroosuna commented 4 years ago

Creo que encontre el problema, la clase Posicion tiene declarados como const x e y. entonces para poder "modificar" una posicion, up(), down(), left(), right() crean una nueva instancia de Posicion cada vez, y el garbage collector parece no limpiarlos. entonces despues de una cantidad n (imagino que dependiendo de la compu) se empieza a ralentizar, y es cada vez peor.

en mi tp defini otra clase con X e Y variables (var property x, var property y) y si quiero modificarlas, lo hago directamente (creo una sola instancia de esa posicion al principio y ya ) y no tiene una baja de performance aparente de esta forma. (incluso reimplementando la abstraccion up() down().. etc)

mientras entienda los mensajes x() e y() parece funcionar bien con el resto de wollok game

leandroosuna commented 4 years ago
class Position {
  const property x = 0
  const property y = 0

  /**
   * Returns a new Position n steps right from this one.
   */    
  method right(n) = new Position(x = x + n, y = y)

  /**
   * Returns a new Position n steps left from this one.
   */    
  method left(n) = new Position(x = x - n, y = y)

  /**
   * Returns a new Position n steps up from this one.
   */    
  method up(n) = new Position(x = x, y = y + n)

  /**
   * Returns a new Position, n steps down from this one.
   */    
  method down(n) = new Position(x = x, y = y - n) 
fdodino commented 3 years ago

Bueno, la primera prueba que hice fue la siguiente: dejé corriendo el programa (solo que necesitaba ver el tablero). Después de un tiempo saqué la foto:

wollokGamePosition1

La segunda foto la saqué 6 minutos después, donde sí noté lentitud en la consola, pero fíjense que la memoria prevista por el Eclipse seguía en el máximo de 1305 MB (no se movió), solo se incrementó lo que estaba utilizando un poquito, y ese poquito se notó en el htop, pero no hubo alteraciones significativas que estén hablando de memory leaks o de un garbage collector que no se active:

wollokGame-position2

Por el contrario, lo que creo que no solo el Garbage collector está funcionando, sino que es la causa del problema: al crearse y descartarse las posiciones en ticks tan seguidos, está tomando tiempo recorrer y eliminar las referencias que no están usadas.

Hasta el 2019 Position era un objeto mutable, con @PalumboN y @npasserini decidimos ir por una opción inmutable, yo creo que para la mayoría de los juegos que he visto funciona bien, mi propuesta es tener una MutablePosition cuya implementación sea la anterior, volviendo a la idea de una var property x, y. Una segunda idea que barajaba era tener un objeto que, en el momento de construir el tablero, genere las posiciones válidas del tablero y que los métodos up, down, left y right le pidan esa posición al tablero, para que ya estén cacheadas las posiciones. Entiendo que esta es una prueba de concepto y no tendría sentido en un juego normal tener 1024x1024 posiciones si el tablero es de 20x20.

Si querés nos ponemos un día a pelotearlo @PalumboN (y @JuanFdS)...

JuanFdS commented 3 years ago

@fdodino Ahhh mirá, puede ser eso, gracias por la investigacion :clap: .

No se muy bien ni por donde arrancar a medir así que con eso seguro necesite una mano o leer más, pero cosas que se me ocurren como alternativas a tener toodas las posiciones ya creadas de entrada es que se guarden en algún lado pero se vayan creando lazy, y tal vez se podría limitar a un pool de N posiciones cacheadas.

PalumboN commented 3 years ago

Ufff qué piola todo esto @fdodino. Yo no sé leer muy bien el top, así que me voy a guiar más por el resumen que tiraste que sí lo entiendo :P

Por el contrario, lo que creo que no solo el Garbage collector está funcionando, sino que es la causa del problema: al crearse y descartarse las posiciones en ticks tan seguidos, está tomando tiempo recorrer y eliminar las referencias que no están usadas.

Concuerdo de que, por lo que interpreto, no hay memory leaks. Perooooo, si es así como decís, qué cambia de los primeros ticks a los ticks que se producen después de 6 mins?? El GC no debería estar funcionando siempre igual?? Porque no es que con el tiempo cada vez hay más referencias, si el GC funciona como esperamos entonces se mantendrían constantes todo el tiempo... O me estoy perdiendo de algo??


Sobre las alternativas para workarounds:

fdodino commented 3 years ago

Hola @PalumboN ,

Concuerdo de que, por lo que interpreto, no hay memory leaks. Perooooo, si es así como decís, qué cambia de los primeros ticks a los ticks que se producen después de 6 mins?? El GC no debería estar funcionando siempre igual?? Porque no es que con el tiempo cada vez hay más referencias, si el GC funciona como esperamos entonces se mantendrían constantes todo el tiempo... O me estoy perdiendo de algo??

Mi hipótesis es que al comienzo tarda menos porque no hay objetos para garbage collectear. Es muy significativo cómo cuando pasan 5 / 10 minutos el println tarda, y para mí tarda más porque hay otras operaciones de mayor prioridad que le ganan al println de la consola. Podrían ser los sucesivos malloc para reservar memoria para los nuevos objetos (son más o menos 400 MB los que necesita), pero ese tamaño yo lo he visto con otras cosas y no noté que hubiera una ralentización del sistema. Entonces le apunto a que el GC al principio no tiene laburo, pasados 2 / 3 minutos tampoco porque conserva en el edén todas esas posiciones, por si las moscas, y cuando llegaste a 5 / 6 minutos, ya tiene que eliminar bocha de direcciones + otras que tiene que meter en el edén, y ahí no le queda otra que estar laburando mucho tiempo.

Yo creo que algo similar puede pasar si jugás con los strings.

Igual ojo, tiene un onTick de 1, eso es un caso borde de acá a la China. Pero para entretenernos sirve.


Después, creo que la opción MutablePosition también es la que más me convence, porque pone la responsabilidad en quien diseña el juego.