【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

色々な言語でDeflate

以前、Deflateの圧縮アルゴリズムを変えながらヒッパルコス星表の圧縮時間を計測しました。

最低圧縮率の方式はVBAへの移植を考えて実装しました。そこで今回、実際にVBAに移植しました。結論から言うとインタプリタ方式のVBAは、JIT方式のF#の25倍ほど遅かったです。

今回計測に使用したコードは以下にあります。

チューニング

まだ改善の余地があると感じたため、以下の手順でチューニングを試みました。

  1. チューニング前
  2. CRC削除
  3. 他の方式を削除
  4. ハッシュの読み出しバグを修正して圧縮率を向上
  5. バッファ移動時の再ハッシュを抑制
  6. 長さ・距離をテーブル化
  7. WriteBitの引数をboolに変更
  8. WriteBitsをテーブルで高速化

処理速度は倍程度に向上しています。(単位:秒)

f:id:n7shi:20120418005724p:plain

他言語移植

以下の言語に移植しました。

処理速度を計測しました。Visual Studio系は2010のExpress Editionです。g++のReleaseは-O3、Debugは-O0です。比較対象として.NET Framework標準のDeflateStreamも計測しました。(単位:秒)

f:id:n7shi:20120418011625p:plain

.NET言語同士だとあまり違いはありませんでしたが、ネイティブのC++は速かったです。Andromedaという独自言語もネイティブではあるのですが、最適化がまったくない上、出力するコードの効率も悪く、最適化しないC++の3倍近くも遅いという結果になりました。

Deflate圧縮の効率

以前、F#でDeflateアルゴリズムの圧縮・展開プログラムを作りました。

先日のZIP実装会 Part.018よやさんと圧縮についてお話させていただき、ハッシュについて実験していたことを思い出しました。せっかく実験したので記録を残しておきます。

Deflateではデータの前方32KBから一致パターンを探すのですが、原始的にリニアサーチするととんでもなく時間が掛かります。ヒッパルコス星表で実験したところ、1時間以上掛かって23.7MBに圧縮できました(固定ハフマン)。

リニアサーチはやめてハッシュを試みました。Deflateでは繰り返しの長さが3バイト以上となっているため、ハッシュのキーも3バイトとしました。適当に作った式で0~4095にハッシュします。ハッシュの範囲が小さいため頻繁に衝突します。そのためハッシュ先をリストにしてデータが失われないようにしています。

let inline getHash (buf:byte[]) pos =
    ((int buf.[pos    ]) <<< 4) ^^^
    ((int buf.[pos + 1]) <<< 2) ^^^
    ( int buf.[pos + 2]       )

以下の3つのアルゴリズムを実装して比較しました。

  1. 規格上許される距離(32KB)すべてのデータを保持する。
  2. 最初の2バイトが同一のものを排除した上で、規格上許される距離(32KB)すべてのデータを保持する。
  3. 最初の2バイトが同一のものを排除した上で、1つのハッシュにつき16のスロットを用意して循環リストとする(4096 * 16 = 65536)。スロットが一巡するとデータが上書きされるため、ほとんど近距離のデータしか保持できない。

私が実装したのは固定ハフマンのみです。先ほどのヒッパルコス星表で実験した結果は以下の通りです。

  1. 7分15秒 23.7MB
  2. 57秒 24.8MB
  3. 35秒 27.9MB

【追記】更に高速化しました ⇒ http://7shi.hateblo.jp/entry/2012/04/18/011748

1は時間が掛かる割に圧縮率はあまり向上していません。ヒッパルコス星表に限れば2で十分のようです。ちなみに1はリニアサーチによる圧縮と出力が完全に一致して、固定ハフマンによる理論上最高値だと思われます。遅すぎて実用には耐えないレベルですが、それでもリニアサーチよりかなりましです。

比較として7zipで動的ハフマン圧縮してみました。

  • ZIP形式(Deflate) 標準圧縮: 31秒 16.8MB
  • ZIP形式(Deflate) 超圧縮: 3分54秒 16.6MB

固定ハフマンの限界をあっさり超えています。超圧縮がどういう風にアルゴリズムを変えているのかは調べていませんが、時間が掛かる割にあまり圧縮率は向上していないので、この辺がDeflateの限界のようです。ヒッパルコス星表のデータに限って言えば、標準圧縮の方がコストパフォーマンスが良いです。

なお、参考までに別アルゴリズムですがLZMAの結果も載せておきます。

  • 7z形式(LZMA) 標準圧縮: 1分27秒 13.6MB

一応フォローしておくと、LZMAは圧縮は重めですが展開は高速です。

カスタムハフマン符号

ZIP勉強会(id:n7shi:20110529)を開催しましたが、時間の都合もありカスタム(動的)ハフマン符号については「RFC 1951を読んでください」で説明を省略しました。IO_Zlib開発者のid:yoyaさんからコメントを頂いたので、某「例示は理解の試金石」を実践してみました。

  • よやさんのまとめ id:yoya:20110726:io_zlib

.NETのDeflateStreamはどんな短いものでもカスタムハフマンで圧縮します。それを利用して "aaaaa" というASCII文字列をカスタムハフマン圧縮してみます。

続きを読む

勉強会 完了報告

id:n7shi:20110417で告知したZIP勉強会が無事に完了しました。ご参加の皆様、お疲れ様でした。スライドと資料を公開します。

以下、参加者の方々のレポートです。私の気付いた範囲で追加していますが、漏れがあれば教えてください。

  • id:takahirox:20110605:1307259596 自作ツールでZIP解析(英語)
  • id:takahirox:20110628:1309267617 自作ツールで無圧縮ZIPを作成(英語)
  • id:takahirox:20110723:1311406642 無圧縮ZIPでCRC-32の計算(英語)

勉強会

ZIP圧縮についての勉強会です。ファイルが格納される構造(コンテナ)、エラーチェック(CRC-32)、圧縮アルゴリズム(Deflate)について説明します。

資料は以前ブログにアップしたもの(id:n7shi:20100905, id:n7shi:20101003)をベースに、新規に書き起こしたりする予定です。

圧縮・展開アプリ

F#でZIPの圧縮・展開を行うアプリを作成しました。GUIデザイナーの関係上、GUI部分はNemerleで作成しました。パブリックドメインで置いておきます。

Deflateの展開は規格通りに実装していますが、圧縮は固定ハフマンのみサポートしています。そのため圧縮率はあまり良くありません。