こんにちは。id:kfly8 です。普段はヒューマンリレーションズ部でエンジニア組織開発をしています。
先日、ISUCON *1でPerlの参考実装をやらせてもらったのですが、とても楽しかったです!貴重な機会をありがとうございました。また、"あのISUCON"の運営裏側を見れて、苦労、凄さなど身近な所で感じることができました。 微力ながら協力できて嬉しかったです。
この記事では、Goの参考実装からPerlへの移植をして考えたことを書きたいと思います。今後、移植をされる方の何かの参考になれば幸いです。注意として、ここでの考えは公式の見解ではなく、あくまで個人的な見解です。
できるだけGo実装に寄せる
移植は、できるだけオリジナル実装のGoに寄せるよう心がけました。 実装の乖離が大きいと競技としてフェアでない、移植ミスの際に気づきやすくなりそう、そんなことが理由です。
具体的には、次の2つを行いました。
- Cpanel::JSON::XS::Typeで、JSONレスポンスを明示
- 返り値でエラーも返してみる
1. Cpanel::JSON::XS::Typeで、JSONレスポンスを明示
これまでのISUCONのPerl実装では、ベンチマーカーの期待通りのJSONレスポンスをエンコードするために、JSON::Typesを利用していました。 ですが、今回はCpanel::JSON::XS::Typeを利用し、JSONレスポンスの構造を明示的にしました。
# JSONレスポンスを明示しておく use constant Chair => { id => JSON_TYPE_INT, name => JSON_TYPE_STRING, description => JSON_TYPE_STRING, thumbnail => JSON_TYPE_STRING, price => JSON_TYPE_INT, height => JSON_TYPE_INT, width => JSON_TYPE_INT, depth => JSON_TYPE_INT, color => JSON_TYPE_STRING, features => JSON_TYPE_STRING, kind => JSON_TYPE_STRING, popularity => undef, stock => undef, }; use constant ChairSearchResponse => { count => JSON_TYPE_INT, chairs => json_type_arrayof(Chair), };
Goのstructを使って明示する書きっぷりに、雰囲気は似ている(?)と思います。JSONレスポンスの構造が把握しやすいメリットだけでなく、パフォーマンス面でもJSON::Typesより優位になります。通常のJSONエンコードは、値が文字列か数値かといった内部の状態を確認してエンコードしますが、この場合、値が何であれ型宣言の通りエンコードを試みます。JSON::Typesの実行コスト分、エンコード速度は優位になります。JSON::Typesは、簡単、簡潔に利用できるメリットがありましたが、今回は変更してみました。
2. 返り値でエラーも返してみる
返り値でエラーも返すようにしてみたのですが、効果は特に得られなかった趣味の話です。
例えば、普段であれば、system(@cmd) or die '..'
と異常時はorで繋ぐところ、次のように書いていました。
my $err = system(@cmd); if ($err) { ... }
range_idからrangeを取り出すget_range関数も、エラーも返すようにしました。
my ($chair_price, $err) = get_range($CHAIR_SEARCH_CONDITION->{price}, $price_range_id); if ($err) { ... }
次のようにget_rangeを剥がすこともできましたが、参考実装のGoに寄せました。
my $chair_price = $CHAIR_SEARCH_CONDITION->{price}{ranges}{$price_range_id}; if (!$chair_price) { ... }
ただPerlの場合、例外で処理するケースがどうしても混ざるので、一貫性が出せず、中途半端でした。趣味でした。
余談
移植は、予定スケジュールよりも早く参考実装を運営チームが用意してくれたので、かなり前持って作業を始めることができました。ただ、悩んだ所、躓いた所もあったので、早めに始められて助かりました。特に次のような所で悩みました。
perlのビルドにuselongdoubleオプションを利用
perlのビルドオプションにuselongdoubleを利用しました。uselongdoubleは、拡張倍精度浮動小数点を扱うためのビルドオプションで、perl -V:nvsize
が8でなく16になります。変更した理由は、緯度経度で桁が足りなくなる為です。例えば、緯度:34.560727610897644 が、緯度:34.5607276108976 となってしまっていました。他の選択肢として、任意桁を扱うモジュール(Math::BigFloatなど)を考えましたが、Go実装と乖離が大きくなりそうな為、不採用にしました。オチとして、PHPでも同様の問題を抱え、結局ベンチマーカーのチェックは緩くなりました。今回の移植で一番悩み、不安だった所です。
isa operator
せっかく、perl5.32を採用したので、本筋でないところにしれっと利用しました。
eval { if ($err) { die $self->res_no_content($c, HTTP_NOT_FOUND); } } if ($@) { # $@が blessされているかどうかとかいちいち確認しなくて済んで便利 return $@ if $@ isa Plack::Response; }
isa operatorはさらっと書けていいですね。ただ、そもそも、evalでなく、try/catchを使う方が直感的で実務の場面に近いと感じるので、evalを無くすかどうか迷いました。Syntax::Keyword::Tryを採用すれば、次のように簡潔に書けるのですが、Try::Tinyを普段利用している人にとっては逆にハマりどころになると思い、今回は見送りました。Syntax::Keyword::Tryの魅力については、papix氏の記事を参照ください。
use Syntax::Keyword::Try; try { if ($err) { # ここでreturnしても、直感通り動く。(普通のことなんだけど・・!) return $self->res_no_content($c, HTTP_NOT_FOUND); } } catch { ... }
cpmを採用
CPANインストーラーに、cpmを利用しました。速いですね。Perlの公式系でもcpmを利用されている例があり、採用しても受け入れてもらえると思い、入れさせてもらいました!
秘密のアレ
ISUCONの恒例(?)でしょうか。気づいてもらえたみたいで、よかったです。
ISUCON予選のperl実装。今回はセッションがなかったわけだけど、恒例のtagomorisは残っていたのでウケた
— 達人が教えるつぶあん🇺🇦 (@kazeburo) 2020年9月13日
おわりに
初めての移植でしたが、本番一発勝負で受け入れてもらえるか、きちんと動作するか緊張感がありましたが、その分充実感も大きかったです。楽しかったです!ありがとうございました!