Composer reduce su consumo de memoria hasta un 30% gracias a un programador español
Composer es el gestor de paquetes/dependencias utilizado por las aplicaciones PHP. Gracias a Composer, las aplicaciones sólo tienen que indicar las librerías que quieren usar, y Composer se encarga del resto, como por ejemplo averiguar la versión exacta que hay que instalar de cada librería para que todas sean compatibles entre sí.
La única pega de Composer es que consume mucha memoria. En aplicaciones muy
complejas puede llegar a necesitar más de 2GB de RAM para resolver todas
esas dependencias. Por eso es común ejecutarlo como
php -d memory_limit=-1 composer ...
para permitirle usar memoria ilimitada.
Los responsables de Composer son conscientes de este problema, pero las mejoras para reducir el consumo de memoria no han sido muy significativas en los últimos años. Sin embargo, el pasado 21 de febrero, un programador español llamado Rubén González creaba este pull request prometiendo reducir el consumo de memoria de Composer hasta un 30%.
Tras el correspondiente proceso de revisión y verificación, el pull request ha sido aceptado y la próxima versión de Composer ya incluirá esta mejora. Las comparaciones de rendimiento definitivas son:
Proyecto | Memoria Antes | Memoria Después |
---|---|---|
Sylius | 983 MB | 539 MB |
Symfony Demo | 699 MB | 383 MB |
Proyecto Symfony complejo | 1.000 MB | 536 MB |
Nos hemos puesto en contacto con Rubén para conocer más detalles sobre su gran trabajo y el ha accedido amablemente a responder a nuestras preguntas:
Para empezar, cuéntanos un poco sobre ti
Me llamó Rubén González (@rubenrua) y soy un teleco de Vigo que actualmente trabaja en Teltek Video Research, empresa tecnológica dedicada a la innovación en soluciones abiertas de video educativo.
Si digo que hace un par de años estaba loco con Scala y ahora intento aprender todo lo que encuentro sobre Machine Learning os podéis imaginar un poco como soy. Llevo trabajando con Symfony desde la versión 1.0 y tengo un cariño especial por este framework y su comunidad.
Para los que no conocemos los detalles internos de Composer, ¿nos podrías explicar las claves de tu solución para reducir el consumo de memoria?
Para entender las claves, hay que unir los puntos que me llevaron a este resultado:
- He usado blackfire.io varias veces para conseguir resultados increíbles en pequeños proyectos locales.
- Le prometí a la gente de PHPVigo que iba a dar una charla de blackfire.io para una de sus reuniones.
- Un día, el responsable de sistema de Teltek, mi amigo Pablo, se quejó de que Composer usaba mucha memoria en nuestros entornos de staging on premise, y que era difícil justificar al cliente el añadir más memoria. Lo solucionamos usando swap.
Así que, cuando me puse a preparar dicha charla, pensé que estaría bien hacer una pequeña contribución a Composer y contarlo. Pero cuando empezé a realizar profilers con Blackfire y a leer el código fuente de Composer, me di cuenta que no iba a ser fácil. Composer ya está muy optimizado, sobre todo en lo referente a tiempo de respuesta (más info).
Composer usa una clase llamada Rule
para resolver las dependencias, con ella
indica los paquetes a instalar, borrar, dependencias, conflictos, alias, etc. Para un
proyecto de tamaño grande, dicha clase se instancia muchas veces (
sobre 1 millon de veces en Sylius).
Es fácil pensar que si optimizas un poco dicha clase, el resultado final será
grande, pero la clase ya estaba muy optimizada para no usar memoria; por ejemplo
usando máscara de bits para minimizar el tamaño de su estado. Para explicar mi
optimizacion la clase Rule
se puede resumir en:
class Rule { const BITFIELD_TYPE = 0; const BITFIELD_REASON = 8; const BITFIELD_DISABLED = 16; public $literals; public function __construct(array $literals, $reason, $reasonData) { $this->literals = $literals; // un array de integers $this->reasonData = $reasonData; // referencia al objeto origen de la regla // máscara de bits para el estado $this->bitfield = (0 << self::BITFIELD_DISABLED) | ($reason << self::BITFIELD_REASON) | (255 << self::BITFIELD_TYPE); } }
¿Cómo llegaste a descubrir esa solución?
Llevando a mi hija a la guardería, después de estar toda la noche poniéndome al
día en un curso de ML, tuve una idea feliz: ver la distribución del tamaño de
literals
. Y ¡bingo! La mayoría de las reglas eran conflictos (80%), cuyo array
solo tenía dos elementos. Si un array ocupa sobre 200 bytes, con una pequeña
refactorización podía reducir 1.000.000 * 0,8 * 200 bytes
(sobre 160MB)
(más info a bajo nivel),
class ConflictRule { public $literal1; public $literal2; public function __construct($literal1, $literal2) { $this->literal1 = $literal1; $this->literal2 = $literal2; // ... } }
Esta primera mejora se puede ver en mi pimer commit
Después de hacer un pull request con está mejora, encontré otra mejora
decrementando el uso de arrays en el proceso de eliminación de las reglas
duplicadas. Las reglas se agrupan en otra clase llamada RuleSet
donde se
eliminan los duplicados de forma muy óptima en tiempo, pero no tanto en memoria.
Para eliminar los duplicados se genera un hash de cada regla y para eliminarlos sólo se comparan la reglas que tienen el mismo hash (denominados duplicados potenciales). Con el diseño realizado, la probabilidad de que dos reglas tengan el mismo hash y sean distintas es muy pequeña, y de esta probabilidad obtuve la segunda mejora. Sólo tuve que postponer la creación del array donde se almacenan los duplicados potenciales hasta que aparezca el primer duplicado potencial.
Esta segunda mejora se puede ver en mi segundo commit
¿Crees que todavía hay un margen de mejora significativo para seguir reduciendo el consumo de memoria de Composer, o estamos acercándonos al límite?
Composer es una herramienta que ya está muy optimizada y será difícil mejorarla. Dicho esto, tampoco creo que sea imposible hacer pequeñas mejoras.
Tras las pruebas realizadas con Blackfire, la otra parte del código que más
memoria consume es procesar los datos del servidor (ver json_decode
). A lo
mejor se pueden arañar unos pocos MB en esa parte.
Por otro lado, no me cuesta nada pensar en una extensión de PHP que mejore exponencialmente el performance de composer, o incluso una implementación de composer en otro lenguaje más óptimo para este tipo de herramientas (como Golang, lenguaje que empieza a usar públicamente Fabien Potencier)
¿Cómo ha sido el proceso de contribución a un proyecto tan importante como Composer?
En mi día a día toco muchas tecnologías diferentes y aunque critico mucho a PHP como lenguaje de programación, tengo que reconocer que mi experiencia realizando contribuciones a Symfony siempre han sido muy positivas. Recuerdo con alegría mi primera contribución, y eso que sólo eran la traducciones a Gallego de los mensajes de validación. En este caso, con Composer, mi experiencia contribuyendo también fue muy buena, sólo que tuvo mucha más repercusión.
Una de las ventajas que te aporta contribuir a estos proyectos es el feedback que recibes. Aprendiendo siempre cosas nuevas.
Muchas veces veo a programadores que tienen respeto por hacer contribuciones a proyectos libres y no lo entiendo. Siempre que se realiza una contribución hay que ponerse en el papel del que la recibe, e intentar hacer su vida lo más fácil posible. Pero la comunidad te devuelve con creces los que les das.
Y lo que digo siempre a los jóvenes programadores, a día de hoy no hay mejor CV para un programador que GitHub.
¿Cuál ha sido el papel de la comunidad PHPVigo en tu contribución?
Como ya dije PHPVigo fue el detonante de esta contribución. Felix y Sergio están haciendo un gran trabajo y es difícil no aprender algo nuevo en cada meetup. Y lo más importante, como dice David Bonilla, "La gente no acude a una charla para aprender, sino para encontrar inspiración y motivación."
Vigo se está moviendo mucho a nivel TIC y a día de hoy tenemos comunidades de todo: PHPVigo, PythonVigo, VigoLabs, JUGVigo, GDGVigo, AgileVigo, SysAdsminGalicias, etc. Es algo muy bueno para la ciudad.
Me gustan mucho las citas y me gustaría cerrar la entrevista con una que describe muy bien mi experiencia con el mundo del software y la optimización:
You can optimise for execution speed. You can optimise for space. But the most precious thing you should optimise for is understandability
Comentarios
Proyectos Symfony destacados
La plataforma de eCommerce 100% Symfony que rivaliza con Magento y PrestaShop. Ver más
Síguenos en @symfony_es para acceder a las últimas noticias.