Digest::BLAKE2作成で学ぶモダンPerl XSモジュール作成と、挫折

Perlを3行書くと死ぬ奇病にかかってはや数年、毎日青息吐息で出社している僕です。一番好きな言語はC言語です。裏切らないから。

というわけで、裏切らないC言語でPerlのモジュールがかければ死ぬ可能性が低まるのではないか、という仮説のもとに、XS module作成を習作してみよう。

BLAKE2というとよさげなハッシュアルゴリズムがあるので、それをPerl module化してみた。

ベンチマーク

まずはベンチマークから。

BLAKE2には4つのアルゴリズムがあるが、マルチプロセス版はセグフォしたので、BLAKE2bとBLAKE2sのみ。

BLAKE2bは64bit CPUで高速に動き512bit出力。BLAKE2sは8-32bit CPUで高速に動き256bit出力。

ビット数あたりの早さでは、MD5を超え、MurmurHashに肉薄している。結構速いっすね。cmptheseでRateの割り算ができればもっときれいに比較できるかな。

                        Rate SHA512    (512bit) SHA1      (160bit) BLAKE2b   (512bit) BLAKE2s   (256bit) MD5       (128bit) MurmurHash(256bit)
SHA512    (512bit)   70572/s                 --                -0%               -90%               -92%               -94%               -95%
SHA1      (160bit)   70671/s                 0%                 --               -90%               -92%               -94%               -95%
BLAKE2b   (512bit)  704225/s               898%               896%                 --               -15%               -41%               -54%
BLAKE2s   (256bit)  833333/s              1081%              1079%                18%                 --               -30%               -45%
MD5       (128bit) 1190476/s              1587%              1585%                69%                43%                 --               -21%
MurmurHash(256bit) 1515152/s              2047%              2044%               115%                82%                27%                 --

どんな風に書いたか

BLAKE2のアルゴリズムごとに、pmとxsのソースコードを自動生成するようにしてみた。

また、今時hexとbase64だけではつらいので、urlsafeなbase64と、Ascii85を追加してみた。basE91とかも入れようかと思ったけど見送り。

Base64, urlsafe Base64, Ascii85へのエンコードは、爆速をうたうエンコードライブラリであるstringencodersを使った。

stringencodersのAscii85は、Adobe式というか、PostScript/PDFで使われているものに近いらしく、Convert::Ascii85とは文字セットが異なる。

普通のAscii85だとテーブルルックアップする必要はないのだが、今回はstringencodersのmodb85gen.cをいじってテーブルの配置を変えた。

また、big endianかどうかをstringencodersに教えないといけないので、use Config; $Config->{byteorder}; でバイトオーダを取得してconfig.hを書き出すようにしてしのいだ。

悩んだところ

今回、typester先生のXSにまつわる話スライドを見ながらXSモジュールを書いてみた。

以下のようなことで悩んだ。

Module::Build::Pluggable::XSUtilを使っているモジュールが少ない。ただでさえ、Build.PLを使っているモジュールが少ないのに、情報量が少なくて詰んだ。typester先生のEV::Hiredisを見ながらしのいだ。

Module::Build::PluggableとModule::Build->subclassを共存させる方法がよくわかんなくて、ACTION_codeでソースコードテンプレートからソースを生成したかったけど、Build.PLのトップレベルでソースコードを生成してしのいだ。Module::Build::Pluggableのソースコードを見る限り、そう簡単にはいかないっぽい。 mRuby.pmのBuild.PLのように、 自分でppport.h生成とc99 checkをやらないといけないのかもしれないけど、ちょっと面倒。

ソースコードテンプレートに変更があっても、Buildで検出してくれない。ソースコードを生成しなおして、Buildできるようにしたい。 NanaのBuild.PLのようにすれば、うまく自動生成をハンドリングしてくれるのかな。

shipitしようとすると、Digest-BLAKE2-0.01/というディレクトリが消えずに残っていて怒られた。.gitignoreとMANIFEST.SKIPに書いてしのいだがなんか不吉な予感する。

CPANにソースコードテンプレートであるBLAKE2x.pmファイルがインデックス化されている。そのモジュールはインストールされず、別名のモジュールがインストールされちゃいます。

CPANにblake2ディレクトリのREADMEがインデックス化されている。

今でもh2xsを最初に使うべきなのかな。今回はh2xsを全く使わず。

typemapというファイルの存在を知らずにハマったのが一番時間を食った。h2xsを使わないのがよくないのか。

まとめ

初めてのXS moduleとそのCPANリリースをやってみた。

一応それなりに高速に動作するハッシュモジュールができて満足だが、パッケージングやビルドまわりでは手詰まり感。XSよりModule::Buildについて調べるのに時間がかかってしまった。モダンXSってどう書くのがいいのか、ある程度テンプレがほしいですね。

ベンチマークコード

use Benchmark qw(:all);

use Digest::MD5;
use Digest::SHA;
use Digest::MurmurHash;
use Digest::BLAKE2;

sub md5 {
  Digest::MD5::md5_hex($_[0]);
}

sub sha1 {
  Digest::SHA::sha1_hex($_[0]);
}

sub sha1 {
  Digest::SHA::sha512_hex($_[0]);
}

sub murmur {
  unpack('H*', Digest::MurmurHash::murmur_hash($_[0]));
}

sub blake2b {
  Digest::BLAKE2::blake2b_hex($_[0]);
}

sub blake2s {
  Digest::BLAKE2::blake2s_hex($_[0]);
}

cmpthese(1000000, {
    'MD5       (128bit)' => sub { md5($str) },
    'SHA1      (160bit)' => sub { sha1($str) },
    'MurmurHash(256bit)' => sub { murmur($str) },
    'BLAKE2s   (256bit)' => sub { blake2s($str) },
    'SHA512    (512bit)' => sub { sha1($str) },
    'BLAKE2b   (512bit)' => sub { blake2b($str) },
});