pbzip2 と php の bzip2 関数

http://compression.ca/pbzip2/

PBZIP2 is a parallel implementation of the bzip2 block-sorting file compressor that uses pthreads and achieves near-linear speedup on SMP machines.

bz2は圧縮後のサイズが小さくていいんだけど、ちょっと遅いのが玉に瑕、でした。pbzip2はブロックごとのソートを複数のCPUで並列処理する、というすごい素直な実装のようです。pbzip2で圧縮したものはbzip2で普通に解凍できるとのこと。

てすと

ということでさっそくテスト。

環境は cpu: Core Duo T2500 2GHz, mem: 2GB, Debian etch, gzip 1.3.5, bzip2 1.0.3, pbzip2 0.9.6 で、15MBくらいのファイル x 8 = 合計115MB を圧縮してみました。ファイルの中身はアクセスログ的なもので、けっこう似たようなことがたくさん書いてあります。

種類 圧縮後のサイズ user cpu total メモリ使用量 解凍時total
gzip 27MB 7.52s 99% 7.674s 0.6M 1.622s
bzip2 19MB 45.17s 99% 45.433s 6.9M 12.364s
pbzip2 19M 53.08s 193% 27.994s 18M 8.820s
pbzip2 -r 19M 52.85s 196% 27.017s 42M 12.834s

ちなみに /usr/bin/time で1回計測しただけ、メモリはtopでRSS目視の最大値です…。

pbzip2 の "-r" は

-r Read entire input file into RAM and split between processors

だそうです。試しにcatで全ファイルをつなげて見たところ、RSSは145Mまで行きましたが、時間はあんま変わりませんでした。もっとどでかいファイルじゃないと意味ないかな。

phpではダメ!!

README見ると、ちょっと不安なことが書いてあります。

If you are writing software with libbzip2 to decompress data created
with pbzip2, you must take into account that the data contains multiple
bzip2 streams so you will encounter end-of-stream markers from libbzip2
after each stream and must look-ahead to see if there are any more
streams to process before quitting. The bzip2 program itself will
automatically handle this condition.

単純にファイル分割して圧縮するだけみたいなので、こういうことになっちゃうんですね。あと、stdinとかpipeとかで入力も(まだ?)できないそうで。

で、phpはどうなのかな、と思って試してみた。

% ls -l all.txt
-rw-r--r-- 1 ichii386 ichii386 115M Nov  3 05:42 all.txt
% php -v
PHP 5.2.4 (cli) (built: Oct 22 2007 23:06:00) 
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies
% php -r "echo file_get_contents('compress.bzip2://./all.txt.bz2');" > all2.txt
% ls -l all2.txt
-rw-r--r-- 1 ichii386 ichii386 900K Nov  3 05:58 all2.txt

おい、900Kってなんだよ!!

ようするに、デフォルトのブロックサイズ(bzip2とおなじ"-9"で変えられる)そのもので、まさにおっしゃる通りになったわけでした。php のえらい人、どなたか直してもらえませんかねぇ…。

追記@30分後

ext/bz2/bz2.c を読んだ感想。なんというか、BZ2_bzread とか使ってる時点でそういうことは想定してませんよ、的なかんじなのかも。BZ2_bzRead を使って BZ2_bzGetUnused をちゃんとやるように大幅に書き換えないとダメそうでした。