XS残酷物語 |
笑畜 |
XSで高速なモジュールが書きたい!
perldoc perlxs
- XS言語リファレンスマニュアル
perldoc perlxstut
- XSUBを書くためのチュートリアル
perldoc perlguts
- Perl APIイントロダクション
perldoc perlapi
- PerlパブリックAPI
……
やった、XSできたよ!
#include "EXTERN.h" #include "perl.h" #include "XSUB.h" MODULE = Foo PACKAGE = Foo SV* hello(SV *sv) CODE: SV *message = newSVpv("Hello, ", 0); sv_catsv(message, sv); RETVAL = message; OUTPUT: RETVAL
$perl -Mblib -MFoo -e"use 5.010; say Foo::hello( shift )" Bob
$perl -Mblib -MFoo -e"use 5.010; say Foo::hello( shift )" Bob Hello, Bob
いぃやぁあぁtたぁ!
貴方が本当に必要だったもの
say 'Hello, ', shift;
XSから |
BY Makamaka @ YAPC::Asia Tokyo 2010 Oct 16 |
アカウント類
http://www.donzoko.net/cgi-bin/tdiary/
twitter: @maka2_donzoko
hatena: makamaka_at_donzoko
github: makamaka
バラバラでわかりにくいです。
本編
XSからPP
XS
Perlと一緒に使うCのコードやCライブラリと、Perlとをつなぐインターフェース記述用言語
- perlxs(in Perl5.12.2)最初のパラグラフの超適当なまとめ
XSで書かれたPerlモジュール
→XSモジュール
XSモジュールを書くには
Perl内部のデータ構造やAPIについて知らないといけない
下手に手を出すとクラッシュするはメモリリークするはたいして効率よくなかったり……
結局メンテもおざなりになりそう……
だから無理にXSモジュールを書く必要はない
Don't write XS!
by miyagawa in Tsukuba.xs #1
だけど、Perlに慣れてくると、もっとPerlのことを知りたくなるのが人情
Perl APIの勉強したい、XSモジュール書いてみたい
でも、とりあえずXSモジュールは
lestrrat氏やgfx氏に任せて
今日はXSをPP化するお話
PPって?
Passion Panty?
(ArcadeGamer FUBUKI)
Poor Perl?
Pure Perl!
What defines "Pure Perl"?
http://www.perlmonks.org/?node_id=709757
Pure Perl
XSモジュールをPP化すると……
ポータビリティの向上
Perl API、XSの知識が得られる
暇つぶし勉強になる
デメリット
色々なXS互換PP
Data::MessagePack::PP (Data::MessagePack)
JSON::PP (JSON::XS)
Math::Random::ISAAC::PP (Math::Random::ISAAC)
Mouse::PurePerl (Mouse::の色々な部分)
Plack::HTTPParser::PP (HTTP::Parser::XS)
Scalar::Random::PP (Scalar::Random)
Set::IntSpan::Fast::PP (Set::IntSpan::Fast)
Text::CSV_PP (Text::CSV_XS)
Text::VisualWidth::PP (Text::VisualWidth)
Text::Xslate::PP (Text::Xslate)
……
PP化
してみる
Step 0
PP化したいXSモジュールを選ぶ
何がPP化に適しているか
Perlで書けるもの
なぜそのモジュールをPP化するのか
Step 1
ファイルを確認
t/*ファイル
*.xsファイル、*.cファイルを確認
typemapファイル
// JSON::XSのtypemap JSON * T_JSON INPUT T_JSON if (!( SvROK ($arg) && SvOBJECT (SvRV ($arg)) && (SvSTASH (SvRV ($arg)) == JSON_STASH || sv_derived_from ($arg, \"JSON::XS\")) )) croak (\"object is not of type JSON::XS\"); $var = (JSON *)SvPVX (SvRV ($arg));
あんまり確認してない。
Step 2
テストが通るようにガシガシ書き始める
PP化の過程で色々わかるようになる
SV *value → $value AV *array → @array HV *hash → %hash CV *code → &code RV *ref → $ref = \$value
まあ、だいたい見たまんま。
なんか似たようなのが多いことに辟易する
SvTYPE(SV* sv) → ref $sv; SvOK(SV* sv) → defined $value SvIOKp(SV* sv) → ああ、整数なんだな SvPOKp(SV* sv)→ ああ、文字列なんだな SvOBJECT(SV* sv) → blessed $obj
どう違うのかと辟易する
HV* gv_stashpv(const char* name, I32 flags) HV* gv_stashpvn(const char* name, U32 namelen, I32 flags) HV* gv_stashpvs(const char* name, I32 create) HV* gv_stashsv(SV* sv, I32 flags)
perlのcall大変ね、と辟易する
// XS.xs(JSON::XS)、encode_rvより GV *to_json = gv_fetchmethod_autoload (SvSTASH (sv), "TO_JSON", 0); if (to_json) { dSP; ENTER; SAVETMPS; PUSHMARK (SP); XPUSHs (sv_bless (sv_2mortal (newRV_inc (sv)), SvSTASH (sv))); // calling with G_SCALAR ensures that we always get a 1 return value PUTBACK; call_sv ((SV *)GvCV (to_json), G_SCALAR); SPAGAIN; // catch this surprisingly common error if (SvROK (TOPs) && SvRV (TOPs) == sv) croak ("%s::TO_JSON method returned same object as was passed instead of a new one", HvNAME (SvSTASH (sv))); sv = POPs; PUTBACK; encode_sv (enc, sv); FREETMPS; LEAVE;
お気楽
// JSON::PP if ( $convert_blessed and $obj->can('TO_JSON') ) { my $result = $obj->TO_JSON(); if ( defined $result and overload::Overloaded( $obj ) ) { if ( overload::StrVal( $obj ) eq $result ) { encode_error( sprintf( "%s::TO_JSON method returned same object as was passed instead of a new one", ref $obj ) ); } } return $self->object_to_json( $result );
まあ、こうやって得られる知識は
『モダンPerl入門』(牧大輔著)の8章読めば書いてありますよ!(涙目)
PP残酷物語
PP化時によく使うかもしれないモノ
B
use B qw( svref_2object ); my $ref = []; my $value = 100; my $b_obj = svref_2object($ref); print "arrayref\n" if $b_obj->isa('B::AV'); $b_obj = svref_2object(\$value); my $flags = $b_obj->FLAGS; if ( $flags & B::SVf_IOK or $flags & B::SVp_IOK ) { # 整数として } elsif ( $flags & B::SVf_NOK or $flags & B::SVp_NOK ) { # 浮動小数として } # 注:文字列のフラグたってるかも
SVp_IOK ^ if ( $flags & B::SVp_IOK ) { # 整数 } elsif ( $flags & B::SVp_NOK ) { # 浮動小数 }
プライベートのp。tieされた変数ではSVf_*OKの代わりにSVp_*OKが立つ場合がある。
通常の変数でもpは立つので、結局pの方をチェックすればよい。
SV関連クラス(Perl 5.11.1以降:Bドキュメントより) B::SV | +------------+------------+ | | | B::PV B::IV B::NV \ / / \ / / B::PVIV / \ / \ / \ / B::PVNV | | B::PVMG | +-------+-------+---+---+-------+-------+ | | | | | | B::AV B::GV B::HV B::CV B::IO B::REGEXP
SV関連クラスのメソッド
他にもSV関連クラス毎に便利なメソッドがある
$b_sv->REFCNT; # 参照カウントを返す $b_iv->IV; # 符号付き整数として $b_iv->int_value; # 符号の有り無しを考慮 # などなど
pack/unpack
ちょっと(どうでも)いい話
Perl 5.9.2からエンディアンを指定できる"<"と">"が加わった。
# ビッグエンディアンのdouble型としてpack $packed_double = pack( 'd>', $double ); # それ以前の場合、使ってる環境がリトルエンディアンの場合はこんな感じ? $packed_double = pack( 'N2', reverse unpack( 'V2', pack( 'd', $double ) ) );
Configモジュール使う
実行してるPerlのコンフィグ
use Config; my $byteorder_is_LE = $Config{byteorder} =~ /^1234/;
Math::BigInt/Math::BigFloat
Step 3
ブラッシュアップ
とりあえず動くようになったら最適化など
例:文字列のパース
// Text::CSV_XSのCSV_XS.xsより while ((c = CSV_GET) != EOF) { // ←一文字ずつ取り出して色々判定 .... }
馬鹿正直にPerlで実装すると果てしなく遅い
状況に応じて正規表現などより早いものを検討
$str =~ /koreda/;
ベンチマークとってより良いものを目指したい
エラー時の結果もなるべく同じに(好みの問題?)
Step 4
パッケージ、テスト
バックエンドの統合
XSモジュールが使えればXSを、ダメならPPをバックエンドにして透過的に利用できるようにすると、便利。
use Foo; # Foo::XSかFoo::PPが呼ばれる
バックエンド指定の環境変数
デバッグの為に強制的にXSかPPかを指定できるように環境変数 'PERL_FOO_BACKEND'とか'PERL_ONLY'などを利用
BEGIN { $ENV{ PERL_FOO_BACKEND } = 'pp' } use Foo; # 強制的にFoo::PPが呼ばれる
環境変数の名前
msgpackやjsonのような他言語でも使われるようなものは環境変数'PERL_'で始めることで、他の言語とのバッティングを避ける
ってMarc Lehmannが言ってました
テスト
XSモジュールのテストが全て通るようなPPモジュールがディストリビューションに同梱されているような場合
Module::Install::ExtendsMakeTest
xaicron氏のModule::Install::ExtendsMakeTest(Dev version)が便利かも。
use inc::Module::Install; tests 't/*t'; extends_make_test( before_run_script => 'tool/force-pp.pl', target => 'test_pp', ); # force-pp.pl には # $ENV{ PERL_FOO_BACKEND } = 'pp'; などが書かれている >make test_pp # バックエンドをPPにしてテスト
まとめ
XSモジュールをPP化して
Enjoy PP!!
ご静聴
ありがとうございます