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) }, });