PythonとNimでXZ圧縮を利用する

ファイル圧縮の形式は世にいろいろありますが、昨今はzipがもっとも一般的でしょうか。
zipは圧縮・展開にかかる時間もほどほど、圧縮率もほどほど、そしてWindowsでもLinuxでも対応ソフトウェアあり、など扱いやすさの面でリードしています。

そんななか、特定分野で人気なのがXZ形式。圧縮にはパワーも時間も使いますが、zipよりも圧縮率が高くかつ短時間で伸長できる、という性質があります。圧縮は一度で、何度も伸長する、という例えばソースコード一式やインストーラの配布といった用途に向いていると言えるでしょう。
あくまで圧縮形式、なのでアーカイブ機能はありません。

今回はWindows向けにXZ形式の圧縮を実装します。アーカイブ形式としてはTarを使います。

もくじ:

はじめに

単に使えればよい、という側面もありますが圧縮時の負荷が大きいという点からは、やはり少しでも速いほうがいいですよね。Python (3.x系)ではlzmaモジュールで使えるようになっていますが、今回はNimでも実装して速度を比較してみます。

XZの圧縮機能、ツールは開発元からSDK (XZ Utils)が提供されており、世に出回っているソフトウェアの多くはそれに依存しているもよう。本体はパブリックドメイン、ツール類を含めるとGPLでライセンスされているようです。

検証した環境はWindows 10 Pro, Python 3.6, Nim 0.18.0です。

PythonでのXZ圧縮

直にlzmaモジュールを叩いてもいいのですが、tar作成の関係もありshutilモジュールを利用します。内部的にはlzmaモジュールとtarfileモジュールに依存しているようです。

実装はshutilモジュールがかなり抽象化しているので、1行です。

もとのディレクトリは"path/to/sourcedir"で、サブディレクトリ以下のファイルも含まれます。
返り値(上の例ではres)には作成された結果のファイルのパスが入り、この場合は"path/to/destfile.tar.xz"のように拡張子が付加されたものになります。

第2引数はアーカイブの形式を指定する文字列で、環境依存ですが他にもzip, bztar, gztarなどが利用できます。

NimでのXZ圧縮

実装、といってもスクラッチで作るのは面倒なので、素直にXZ Utilsを使います。
Windows環境なのでビルド済みのDLLを使ってNimから呼び出すようにします。

NimbleにもLZMAのラッパはあるのですが、動かしてみるとどうやらこれはバグっている様子。参考にしつつもファイル圧縮のところのみ抜き出して実装します。

Tar作成には別記事の実装を利用します。中間ファイルを吐かずオンザフライに作るよう実装するのが面倒だったので、一旦tarファイルを作成したのち、XZ形式で圧縮することにします。
XZのエンコーダは圧縮の始めから終わりまでステートフルになっており、ちゃんとオンザフライ(中間ファイルを作成しないやり方)にするならtarファイルを抽象化するストリームで実装することになると思われます。

実際試してみると分かりますが、計算処理の負荷で言えば、tarファイルを作成する処理よりも圧倒的にXZ圧縮の処理のほうが重くなります。
XZ圧縮のプログラム例については後ほど記載します。

速度比較

気になるのは速度ですね。そもそも想定する用途が違う(もちろんshutilのほうが汎用的に作られています)のでapple to appleな比較にはなりませんが、まあご参考までに、ということで。

このテストでは、そこらへんにあったディレクトリを圧縮の対象としました。もとのファイル(tar作成時点で)は約120MB、圧縮をかけると約80MBになりました。
XZの圧縮オプション(preset)はデフォルト(6)、誤り訂正オプションはCRC(64)です。

複数回(7回)実行させた平均値は下記のようになりました。計測のためPythonではtime.time()、Nimではtimes.cpuTime()を使っています。あまり精度は良くないようですが、試行間の差は0~3秒程度でした。

Python 3.6Nim (liblzma.dll)
Time (7-times avg.)50.0[sec]34.3[sec]

Pythonでの実装(スクリプト)とNimでの実装(バイナリ)を用意しておき、PowerShellからInvoke-Expressionでランダムに呼び出しています。

さすが静的型付け言語、Nimのほうが速いですね。もちろん世のプロフェッショナルによる枯れた実装があるなら、わざわざ素人が自作する必要はありません。先達に感謝を捧げつつ利用するのみ。しかしながら、かゆい所を色々試してみるのは楽しいものです。

Pythonの高速化と言えばNumbaやCythonがありますが、外部のモジュールに依存している場合やうまく処理を切り出せる場合、細かくチューニングしたい場合など、静的言語を使うのもアリかもしれませんね。

Nimでのプログラム例

詳細は省略しますが、Nimでliblzma.dllのラッパを実装した例(というか上記のパクリ)が下記です。
xzプロシージャにファイルパスを渡すと、対象のファイルと同じ場所でXZ圧縮をかけます。元のファイルは削除しません。

NimからDLLで定義されている関数を呼んでいるだけなので、Linuxでもshared objectを用意してコンパイルすれば動くと思います。

おわり。