Serializar y comprimir arrays en PHP y JSON
Serializar un array con PHP y JSON
Cuando hice el buscador interno para este sitio lo basé en un índice de palabras clave almacenadas en un array. Realmente hay dos archivos de índices, uno es indice-web.txt con un tamaño actual de 166 KB y el otro es indice-claves.txt que pesa 87 KB. Están almacenados en archivos de texto como arrays serializados. Se trata de convertir los arrays en una cadena de texto plano para poder guardarlo en disco y luego extraerlo. Para la serialización se usa la función de PHP serialize()
. Por ejemplo, supongamos este array sencillo:
$arr = array("naranjas","manzanas","peras");
Lo serializamos con la función anterior y obtenemos esta cadena de texto:
a:3:{i:0;s:8:"naranjas";i:1;s:8:"manzanas";i:2;s:5:"peras";}
También podemos hacer esa serialización usando la notación JSON, con la función de PHP json_encode()
:
["naranjas","manzanas","peras"]
La serialización JSON es ideal para enviar arrays al navegador del usuario y reconvertirlas en el navegador con Javascript. Pero también ambas cadenas podemos guardarlas en un archivo de texto para en un momento posterior recuperarlas con unserialize()
y json_decode()
respectivamente, devolviéndonos los arrays originales. Me interesa en este caso como almacenar un array en el servidor con el índice de palabras claves del buscador interno. Y buscar la forma más rápida de recuperar ese array.
Se observa que la serialización JSON tiene una longitud menor, por lo que podría pensarse que sería más rápida que la serialización normal. Además también podríamos pensar que si comprimimos los datos obtendríamos una mejora.
Comprimir arrays con PHP
Hay un buen conjunto de recursos para comprimir datos con PHP. Para hacer estas pruebas he usado la librería Zlib de PHP. Para comprimir la cadena serializada he usado gzdeflate()
con un nivel máximo de compresión (9). Se descomprime con la función gzinflate()
. Son apropiadas si no vamos a enviar estos datos fuera, es decir, sólo vamos a comprimir y descomprimir los mismos datos en el servidor, pues estas funciones no añaden cabeceras o controles de paridad al final, como podría hacerlo gzencode()
en el formato GZIP, el formato ZIP u otros.
Para los archivos de índices serializados con serialize()
tenemos estos datos:
Archivo | Original | Comprimido | Tasa compresión |
---|---|---|---|
indice-web.txt | 165 KB | 41 KB | 75% |
indice-claves.txt | 87 KB | 21 KB | 76% |
Si serializamos con json_encode()
los archivos originales pesan aún menos (130 KB y 41 KB), ocupando mucho menos los comprimidos.
Archivo | Original | Comprimido | Tasa compresión |
---|---|---|---|
indice-web.txt | 130 KB | 33 KB | 75% |
indice-claves.txt | 41 KB | 14 KB | 66% |
Como esos dos archivos se usan en el buscador interno en cada nueva búsqueda, lo lógico sería usar la serialización JSON y la compresión de archivos. Así tendríamos los pesos de 33 KB y 14 KB por lo que tendrían que ser más rapidas las cargas en array de los índices.
Pruebas de la eficiencia en la recuperación de arrays serializados con PHP y JSON
Tener los datos estructurados en arrays y serializados en archivos de texto en lugar de en una base de datos puede ser una carga extra para el servidor. En cada búsqueda hay que cargar los arrays. Pero si el tamaño de los archivos no es muy grande aún pueden usarse en lugar de una base de datos. Podemos optimizar la carga de los arrays pero sin olvidar que todo dependerá del acceso a disco, es decir, ese aspecto será un "cuello de botella" que no podemos salvar directamente desde PHP, aunque si los archivos están comprimidos puede ser lógico pensar que facilitará ese aspecto. ¿Hasta qué punto conviene comprimirlos?
Por otro lado hay diversas formas de leer un archivo de texto con PHP. He visto que tanto la función fread()
como file_get_contents()
para leer archivos de texto tardan más ocasionalmente en una primera lectura. En las posteriores el tiempo de proceso baja radicalmente debido a que ese archivo se extrae de alguna caché de memoria.
En un apartado del tema sobre buscadores internos que habla sobre los tiempos de lectura de los archivos de índices comenté la necesidad de buscar estructuras de datos y técnicas que nos permitan acceder rápidamente a los índices. Por lo tanto estaba interesado en ver si podía mejorar estos tiempos y para salir de dudas usé el archivo indice-web.txt. Se trata de la serialización de un array multidimensional que guarda los datos del índice (ver estructura de ese array). Hice 4 versiones de ese array:
- indice-web.txt serializado con
serialize()
- indice-web.txt serializado con
json_encode()
- indice-web.gz serializado con
serialize()
y comprimido congzdeflate()
- indice-web.gz serializado con
json_encode()
y comprimido congzdeflate()
Por otro lado he usado las dos funciones de PHP señaladas para leer: fread()
y file_get_contents()
. Por lo tanto tenemos 8 variantes posibles de lectura y carga de array:
Extraer | Descomprimir | Deserializar | Tamaño (KB) |
---|---|---|---|
file_get_contents() | unserialize() | 165 | |
file_get_contents() | json_decode() | 130 | |
file_get_contents() | gzinflate() | unserialize() | 41 |
file_get_contents() | gzinflate() | json_decode() | 33 |
fread() | unserialize() | 165 | |
fread() | json_decode() | 130 | |
fread() | gzinflate() | unserialize() | 41 |
fread() | gzinflate() | json_decode() | 33 |
Con 100 iteraciones de este grupo (total de 800 lecturas), el promedio de cada variante ordenado creciente fue el siguiente (donde serial
se refiere al uso de unserialize()
y json
al de json_decode()
):
EXTRACCION COMPRIMIDO DECODE TAMAÑO TIEMPO (ms) ------------------------------------------------------------ file_get_contents NO serial 165 KB 6.52 fread NO serial 165 KB 6.56 fread SI serial 41 KB 9.26 file_get_contents SI serial 41 KB 9.39 file_get_contents NO json 130 KB 12.27 fread NO json 130 KB 12.34 file_get_contents SI json 33 KB 14.04 fread SI json 33 KB 14.17
Esta prueba la hice con el servidor Apache+PHP en localhost y aunque los valores absolutos no son interesantes (pues depende de la máquina y las tareas que se estén ejecutando en disco en ese momento) si que puede aclararnos algo la comparación entre valores. En todas las repeticiones de la prueba se observan resultados comparados similares. Se consigue la mayor velocidad de extracción y deserialización con file_get_contents
o fread
y unserialize()
. Es más rápido incluso que con las variantes de archivos comprimidos. La deserialización en JSON resulta la más ineficiente en todos los casos.
Antes de hacer estas pruebas pensaba que si comprimía y serializaba JSON obtendría mejor respuesta. Pues bien, es esa combinación la que produce los peores resultados. Por un lado hay un coste añadido en la descompresión y por otro lado vemos que la deserialización JSON también es más costosa que la de unserialize()
.