Me preguntaba si era posible hacer algo más eficiente que descomprimir desde el inicio del archivo hasta el punto. Parece que la respuesta es no. Sin embargo, en algunas CPU (Skylake) zcat | tail
no sube la CPU a la velocidad de reloj completa. Vea abajo. Un decodificador personalizado podría evitar ese problema y guardar las llamadas del sistema de escritura de tubería, y tal vez ser ~ 10% más rápido. (O ~ 60% más rápido en Skylake si no modifica la configuración de administración de energía).
Lo mejor que podría hacer con un zlib personalizado con una skipbytes
función sería analizar los símbolos en un bloque de compresión para llegar al final sin hacer el trabajo de reconstruir realmente el bloque descomprimido. Esto podría ser significativamente más rápido (probablemente al menos 2 veces) que llamar a la función de decodificación regular de zlib para sobrescribir el mismo búfer y avanzar en el archivo. Pero no sé si alguien ha escrito tal función. (Y creo que esto realmente no funciona a menos que el archivo se haya escrito especialmente para permitir que el decodificador se reinicie en un determinado bloque).
Tenía la esperanza de que hubiera una manera de saltar los bloques Deflate sin decodificarlos, porque eso sería mucho más rápido. El árbol Huffman se envía al comienzo de cada bloque, por lo que puede decodificar desde el comienzo de cualquier bloque (creo). Oh, creo que el estado del decodificador es más que el árbol Huffman, también es el 32kB anterior de datos decodificados, y esto no se restablece / olvida a través de los límites de bloque de forma predeterminada. Los mismos bytes pueden seguir siendo referenciados repetidamente, por lo que solo pueden aparecer literalmente una vez en un archivo comprimido gigante. (por ejemplo, en un archivo de registro, el nombre de host probablemente permanece "activo" en el diccionario de compresión todo el tiempo, y cada instancia hace referencia al anterior, no al primero).
El zlib
manual dice que debe usar Z_FULL_FLUSH
al llamar deflate
si desea que la secuencia comprimida pueda buscarse en ese punto. "Restablece el estado de compresión", por lo que creo que sin eso, las referencias hacia atrás pueden ir a los bloques anteriores. Entonces, a menos que su archivo zip se haya escrito con bloques de descarga completa ocasionales (como cada 1G o algo tendría un impacto insignificante en la compresión), creo que tendría que hacer más trabajo de decodificación hasta el punto que deseaba de lo que inicialmente estaba pensando. Supongo que probablemente no puedas comenzar al comienzo de cualquier bloque.
El resto de esto se escribió mientras pensaba que sería posible encontrar el comienzo del bloque que contiene el primer byte que desea y decodificar desde allí.
Pero desafortunadamente, el inicio de un bloque Deflate no indica cuánto tiempo es para bloques comprimidos. Los datos incompatibles se pueden codificar con un tipo de bloque sin comprimir que tiene un tamaño de 16 bits en bytes en la parte frontal, pero los bloques comprimidos no: RFC 1951 describe el formato de forma bastante legible . Los bloques con codificación dinámica de Huffman tienen el árbol al frente del bloque (para que el descompresor no tenga que buscar en la secuencia), por lo que el compresor debe haber mantenido todo el bloque (comprimido) en la memoria antes de escribirlo.
La distancia máxima de referencia hacia atrás es de solo 32 kB, por lo que el compresor no necesita mantener muchos datos sin comprimir en la memoria, pero eso no limita el tamaño del bloque. Los bloques pueden tener varios megabytes de longitud. (Esto es lo suficientemente grande como para que el disco valga la pena incluso en una unidad magnética, frente a la lectura secuencial en la memoria y simplemente omitir datos en la RAM, si fuera posible encontrar el final del bloque actual sin analizarlo).
zlib crea bloques el mayor tiempo posible:
según Marc Adler , zlib solo comienza un nuevo bloque cuando el búfer de símbolos se llena, lo que con la configuración predeterminada es 16.383 símbolos (literales o coincidencias)
Comprimí la salida de seq
(que es extremadamente redundante y, por lo tanto, probablemente no sea una gran prueba), pero pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -c
funciona con solo ~ 62 MiB / s de datos comprimidos en un Skylake i7-6700k a 3.9GHz, con DDR4-2666 RAM. Eso es 246MiB / s de datos descomprimidos, que es un cambio considerable en comparación con la memcpy
velocidad de ~ 12 GiB / s para tamaños de bloque demasiado grandes para caber en la memoria caché.
(Con el energy_performance_preference
ajuste predeterminado en balance_power
lugar de balance_performance
, el gobernador interno de la CPU de Skylake decide ejecutar solo a 2.7GHz, ~ 43 MiB / s de datos comprimidos. Lo uso sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'
para ajustarlo. Probablemente estas llamadas frecuentes del sistema no parecen estar realmente vinculadas a la CPU trabajar para la unidad de administración de energía.)
TL: DR: zcat | tail -c
está vinculado a la CPU incluso en una CPU rápida, a menos que tenga discos muy lentos. gzip usó el 100% de la CPU en la que se ejecutaba (y ejecutó 1.81 instrucciones por reloj, según perf
), y tail
utilizó 0.162 de la CPU en la que se ejecutó (0.58 IPC). El sistema estaba en su mayor parte inactivo.
Estoy usando Linux 4.14.11-1-ARCH, que tiene KPTI habilitado de manera predeterminada para evitar Meltdown, por lo que todas esas write
llamadas al sistema gzip
son más caras de lo que solían ser: /
Tener la búsqueda incorporada en unzip
o zcat
(pero aún utilizando la zlib
función de decodificación regular ) ahorraría todas esas escrituras de tubería, y conseguiría que las CPU Skylake funcionen a la velocidad de reloj completa. (Este downclocking para algunos tipos de carga es exclusivo de Intel Skylake y posterior, que han descargado la toma de decisiones de frecuencia de la CPU del sistema operativo, porque tienen más datos sobre lo que está haciendo la CPU y pueden aumentar / disminuir más rápido. Esto es normalmente bueno, pero aquí lleva a Skylake a no acelerar a toda velocidad con una configuración de gobernador más conservadora).
No hay llamadas del sistema, solo reescribir un búfer que se ajuste en la caché L2 hasta que llegue a la posición de byte inicial que desea, probablemente supondría al menos un% de diferencia. Tal vez incluso el 10%, pero solo estoy inventando números aquí. No he perfilado zlib
ningún detalle para ver qué tan grande es la huella de caché, y cuánto duele el vaciado TLB (y, por lo tanto, el vaciado uop-cache) en cada llamada al sistema con KPTI habilitado.
Hay algunos proyectos de software que agregan un índice de búsqueda al formato de archivo gzip . Esto no le ayuda si no puede lograr que nadie genere archivos comprimidos buscables para usted, pero otros futuros lectores pueden beneficiarse.
Es de suponer que ninguno de estos proyectos tienen una función de decodificación que sabe cómo saltar a través de una corriente desinflarse sin un índice, ya que sólo están diseñados para trabajar cuando un índice está disponible.