¿Cómo mejorar la compresión de NSIS?

Trabajando en FileOptimizer me sugirieron disminuir el tamaño del instalador.

Cómo sabéis, está basado en Nullsoft Scriptable Install System (NSIS), el instalador que se hizo famoso con el desaparecido WinAMP, y estaba configurado para comprimir usando LZMA, que es el algoritmo que aporta mejores resultados sobre ZLib y BZip2. Como podéis apreciar en Setup\FileOptimizerSetup.nsi, lo primero que se hacía era escoger LZMA como algoritmo de compresión:

SetCompressor /SOLID /FINAL lzma

Quedaba ampliar el diccionario de LZMA, que había fijado a 96 Mb. desde hacía algún tiempo. El motivo, que incluso en NSIS 3.01, si lo aumentaba, se producía un error un tanto extraño:

Internal compiler error #12345: deflateInit() failed(initialization failed [-2]).
Note: you may have one or two (large) stale temporary file(s) left in your temporary directory (Generally this only happens on Windows 9x).

¿Cómo mejorar la compresión de NSIS?

Sin duda era confuso, hablaba de Windows 9x (95, 98 y ME), y de archivos temporales, así que lo primero, fue vaciar el contenido de %TEMP%. Como era de esperar, el problema persistía. La clave la dio deflateInit, es decir, la función que inicializa el motor de compresión. Tras una búsqueda rápida por internet, me di cuenta que se debía a una falta de memoria. Nullsoft Scriptable Install System, sigue siendo una herramienta de 32 bits, y aunque en el pasado, hubo intentos para portarla a Win64, no tuvieron un final feliz. Un diccionario de 96 Mb., consume 1.071 Mb. de memoria durante la compresión (98 Mb. durante la descompresión), mientras que uno de 128 Mb., incrementaba la cifra hasta los 1.375 Mb.

Como todos conocéis, los procesos Win32, tienen un acceso máximo a la memoria de 2Gb., independientemente de que tu sistema operativo sea de 64 bits, y de la cantidad de memoria física que tengas instalada. A esos 2 Gb., debemos restarle la cantidad reservada para otros menesteres, y la consumida por el propio MAKENSIS.EXE.

Por ensayo y error, llegué a determinar que el diccionario máximo que podía establecer, era de 121 Mb.:

SetCompressorDictSize 121

Con este cambio, el tamaño del instalador, pasó de pesar 96 Mb. a pesar 92 Mb., mejoró algo, pero no era nada del otro mundo. En cambio, nadie daba ninguna solución al problema. Incluso se me ocurrió probar con las compilaciones diarias de NSIS, para verificar en mis propias carnes, que el problema seguía ahí.

Entonces me topé con 4GB Patch de NT Core, una herramienta similar a PatchPE, que modificaba las cabeceras del ejecutable para establecer la compatibilidad con Large Address Aware (LAA). Sorprendentemente lo probé sobre MAKENSIS.EXE, y fui capaz de aumentar el diccionario hasta 221 Mb., es decir, para que MAKENSIS usara unos 2,3 Gb. de memoria.

Gracias a esto, el instalador se redujo hasta los 70 Mb., una reducción notable, derivada de que en FileOptimizer, muchos plugins se encuentran repetidos entre las versiones de 32 bits y 64 bits. Los que no lo están, comparten también bastante información, y hace que sean mucho mejor comprimibles con un diccionario grande.

La lástima, es que no sea capaz de establecer diccionarios de 384 Mb., que permitirían que todos los archivos en la distribución del instalador estuvieran presentes en el mismo, detectando por tanto muchas más redundancias, y que eventualmente, llegarían a reducir FileOptimizerSetup.exe hasta apenas 67 Mb.

Lo ultimo que quedaba, era actualizar PatchPE, para que también modificara el Large Address Aware, así que dicho y hecho:

PatchPE 1.20 - (c) 2017 by Javier Gutierrez Chamorro
Patches PE executable headers to make them compatible with older versions of Windows

File: \Archivos de programa\Borland\NSIS\Bin\makensis.exe
Operating System Version: 4.00
Image Version: 0.00
Subsystem Version: 4.00
Large Address Aware: Yes (>3GB)

Patched successfully!

¿Cómo mejorar la compresión de NSIS?

Ahora ya podemos sin miedo ejecutar makensis, disponiendo de casi 3 Gb. para su ejecución:

Processing config: D:\Archivos de programa\Borland\NSIS\nsisconf.nsh
Processing script file: "FileOptimizerSetup.nsi" (ACP)

Processed 1 file, writing output (x86-unicode):

Output: "D:\PROYECTOS\FileOptimizer\FileOptimizerSetup.exe"
Install: 2 pages (128 bytes), 1 section (2072 bytes), 672 instructions (18816 bytes), 1271 strings (7884 bytes), 1 language table (266 bytes).
Uninstall: 1 page (128 bytes), 1 section (2072 bytes), 44 instructions (1232 bytes), 374 strings (2366 bytes), 1 language table (226 bytes).
Datablock optimizer saved 7534 KiB (~2.6%).

Using lzma (compress whole) compression.

EXE header size:              168448 / 48640 bytes
Install code:                          (29606 bytes)
Install data:                          (278590247 bytes)
Uninstall code+data:                   (20364 bytes)
Compressed data:            73626634 / 278640217 bytes
CRC (0x28932CBE):                  4 / 4 bytes

Total size:                 73795086 / 278688861 bytes (26.4%)

¿Cómo mejorar la compresión de NSIS?

La mala noticia, es que a los desarrolladores de NSIS, les hubiera bastado con agregar /LARGEADDRESSAWARE a las opciones del enlazador a la hora de generar los ejecutables de NSIS, y así al menos, aumentar el tamaño de diccionario posible. Algo que no les hubiera llevado ni 5 segundos, y que a mi me ha llevado casi 4 horas.

4 comentarios en “¿Cómo mejorar la compresión de NSIS?”

  1. ¿no será porque puede crear conflictos si las DLL usan /LARGEADDRESSAWARE:NO?

    No es por llevarte la contraria pero yo, al igual que los de NSIS, no lo hubiera metido. Más que nada porque si necesitas un espacio tan grande, necesariamente el archivo generado tiene que ser grande. Ahora bien, dada tu afición a comprimir todo lo comprimible, entiendo que lo hayas hecho 😀

  2. Javier Gutiérrez Chamorro (Guti)

    Efectivamente puede crear conflictos bianamaran. Ese modificador, lo que le indica a Windows, es que el desarrollador ha verificado que su aplicación, y todas las DLL que usa, son capaces de trabajar con espacios de memoria superiores a los 2GB (e inferiores a los 4GB).

    Hay casos en los que eso no es cierto. Pero son cosas o mal programadas, o de muy bajo nivel. Lo que yo creo que ocurre, es que en los enlazadores de 32 bits, el valor por defecto es LARGEADDRESSAWARE:NO, y ni se han molestado.

    El archivo generado no es demasiado grande, unos 75 MB, pero LZMA necesita bastante memoria, y ese es el problema.

Deja un comentario