【python】lzmaを用いてデータを圧縮・展開する

 pythonには、3.3からlzmaというデータの圧縮を担うビルトインライブラリが存在します。

 もちろんデータ特性にはよりますが、zipよりも圧縮率が優れているとされています。

 公式ドキュメントはこちら。インターフェースが明瞭なので、誰でも読み解けると思います。

テストデータを作成する

 JSON Generatorという便利なサービスがあります。今回はここで125.1MBほどの、generated.jsonというデータを生成して使用することにします。

メモリ上で操作する

 非常にシンプルなインターフェースである、compressdecompressメソッドが用意されています。

圧縮

import lzma

with open('generated.json', 'rt') as input:
  json_str = input.read()

compressed = lzma.compress(json_str.encode()) # bytesを渡す必要があるので、encode()を使用

展開

 上述した圧縮コードの続きです。

decompressed = lzma.decompress(compressed)
decompressed_json_str = decompressed.decode() # 展開時は、strに戻すdecode()を忘れずに

print(decompressed_json_str == json_str) # -> True

 今回はstrを利用しているので、encode()decode()を使いましたが、バイナリファイル等の場合は、また別の処理が必要になってきます。とにかくbytes-likeに変換して渡してやればよさそうです。

ファイルを直接操作する

 今回生成したような100MBを超えるほどのデータの場合は、メモリ使用量の関係から一旦diskに保存してstream上で圧縮・展開を行うほうが好ましいことが多いです。

圧縮

 先程のgenerated.jsonを直接圧縮していきましょう。

import lzma

with open('generated.json', 'rb') as input:
  with lzma.open('generated.json.xz', 'w') as output:
    input_bytes = input.read()
    output.write(input_bytes)
    output.flush()

 これでも勿論処理は可能ですが、input_bytes = input.read()の行でファイルの内容をすべてメモリに読み取ってしまっています。

 せっかくファイルから読み込むのですから、もう少し工夫してみます。

import lzma

CHUNK_SIZE = 10 * 1024 * 1024 # 1MB

with open('generated.json', 'rb') as input:
  with lzma.open('generated.json.xz', 'w') as output:
    while (chunk := input.read(CHUNK_SIZE)):  # required python > 3.8
      output.write(chunk)
    output.flush()

 このように、データを一定のchunkに分けて読み込み、順次処理を行うことによって、メモリに乗るデータを必要最小限に抑えることができます。

展開

 圧縮を逆向きでやるだけですね。

import lzma

CHUNK_SIZE = 10 * 1024 * 1024 # 1MB

with lzma.open('generated.json.xz', 'r') as input:
  with open('generated.decompressed.json', 'wb') as output:
    while (chunk := input.read(CHUNK_SIZE)):  # required python > 3.8
      output.write(chunk)
    output.flush()

圧縮率比較

 zipと圧縮率を比べてみます。

アルゴリズム(圧縮形式) サイズ 圧縮率
無圧縮 125.1MB 100%
zip 28.9MB 23.1%
lzma 18.1MB 14.4%

速度について

 率直に言ってかなり遅いです。圧縮率で言えばzipとは10%弱の差しかありませんが、速度は大きく劣ります。zip圧縮は殆ど一瞬ですが、lzma圧縮は80秒ほどもかかります。圧縮率10%弱の差をどう見るかですが、重いファイルにはあまり使えなさそうな気がします。

 一方で、展開の速度はかなり早く、1秒ほどで完了するので、圧縮の頻度が低く、かつ展開の頻度が高く、高圧縮率なものが求められる場合は適しています。

 あるいは、数MBほどのデータを圧縮する用途でならあまり問題にならないかもしれません。

まとめ

 lzmaは圧縮率が優れていますが、速度面で少し難があるようです。とはいえ、用途によっては十分使えるので、圧縮アルゴリズムとしては検討の余地があるのではないでしょうか。