26 mayo, 2009

Compresión JSON con JSON.hpack

JSON.hpack es un compresor de conjuntos de datos JSON. Es capaz de reducir hasta un 70% el número de bytes necesarios para representar una colección genérica y homogénea (entendiendo por homogénea la que suele ser producida por una consulta a una base de datos relacional en la que se repite una serie de filas con el mismo número de campos).

Así como un resultado con XML como el siguiente...

  <result>
    <item>
      <name>Andrea</name>
      <age>31</age>
      <gender>Male</gender>
      <skilled>true</skilled>
    </item>
    <item>
      <name>Eva</name>
      <age>27</age>
      <gender>Female</gender>
      <skilled>true</skilled>
    </item>
    <item>
      <name>Daniele</name>
      <age>26</age>
      <gender>Male</gender>
      <skilled>false</skilled>
    </item>
  </result>
  <!-- 286 caracteres (sin contar espacios) -->

... puede ser compactado con JSON a la cadena siguiente...

[
  {
    "name":"Andrea",
    "age":31,
    "gender":"Male",
    "skilled":true
  },
  {
    "name":"Eva",
    "age":27,
    "gender":"Female",
    "skilled":true
  },
  {
    "name":"Daniele",
    "age":26,
    "gender":"Male",
    "skilled":false
  }
]
// 177 caracteres (sin contar espacios)

... podemos ver fácilmente que JSON admite una compresión aún mayor, puesto que cada registro repite los nombres de los campos.

El nivel 0 de compresión con JSON.hpack propone reunir los nombres de los campos en un primer registro:

[["name","age","gender","skilled"],["Andrea",31,"Male",true],["Eva",27,"Female",true],["Daniele",26,"Male",false]]
// 115 caracteres

Aún así, se repiten valores (como true, false, male y female) que podrían ser sustituidos por índices para ahorrar aún más espacio (aunque en este ejemplo con 3 registros el resultado no es mejor que el anterior):

[["name",["Andrea","Eva","Daniele"],"age","gender",["Male","Female"],"skilled",[true,false]],[0,31,0,0],[1,27,1,0],[2,26,0,1]]
// 167 caracteres

Como se puede ver en este nivel 1 de compresión, en el primer registro se indican los nombres de los campos y los posibles valores que pueden adoptar siempre que no sean numéricos (el autor cree que no vale la pena realizar la conversión de unos números por otros (índices)). Por ejemplo, Male o True se sustituyen por 0 y Female o False por 1.

Esto aún puede optimizarse más con el nivel 2 de compresión, comparando si vale la pena enumerar los valores en la cabecera y sustituirlos en los datos por índices o si es mejor mantener los datos (en el siguiente caso, los nombres no se enumeran):

[["name","age","gender",["Male","Female"],"skilled",[true,false]],["Andrea",31,0,0],["Eva",27,1,0],["Daniele",26,0,1]]
// 119 caracteres

Esta comprobación se realiza a nivel de la colección entera. Es posible realizarla a nivel de cada campo, comparando la longitud de la enumeración más los índices que serán necesarios con los de indicar directamente los valores:

// Comparando "gender":
  ["Male","Female",0,1,0] < ["Male","Female","Male"] (23 caracteres es menos que 24)
// comparando "skilled" :
  [true,false,0,0,1]      > [true,true,false]        (18 caracteres es menos que 17)

Según esto, el resultado de aplicar el nivel 3 de compresión será:

[["name","age","gender",["Male","Female"],"skilled"],["Andrea",31,0,true],["Eva",27,1,true],["Daniele",26,0,false]]
// 116 caracteres

Y aún se puede extraer un último nivel 4 de compresión comparando todos los anteriores y seleccionando el que menos ocupe. Es posible hacer pruebas en la página de ejemplos.

Aunque parece que la compresión es importante, hay que tener en cuenta que si el contenido se está comprimiendo con gzip desde el servidor, la reducción real en información enviada será mucho menor. Para conjuntos grandes de datos, sí que puede haber una diferencia importante, aunque se pague el precio del procesamiento necesario para comprimir y descomprimir los datos.
Existen librerías para JavaScript, PHP y C#, aunque según el wiki del proyecto, es posible que se amplíe a Python, Ruby o plugin PHP mediante C.

En concreto, la versión JavaScript cuenta con los siguientes métodos:

  • hpack( Array[, Int[0-4]] ):HArray, convierte una colección homogénea a otra comprimida con hpack
  • hunpack( HArray ):Array, convierte una colección hpack a su original (no es necesario indicar qué compresión se usó)
  • hbest( Array ):Int[0-4], devuelve el nivel de compresión ideal para la colección homogénea especificada

Publicar un comentario en la entrada

Últimos links en indiza.com