Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

ISUCON10でPerlの参考実装をしました

こんにちは。id:kfly8 です。普段はヒューマンリレーションズ部でエンジニア組織開発をしています。

先日、ISUCON *1でPerlの参考実装をやらせてもらったのですが、とても楽しかったです!貴重な機会をありがとうございました。また、"あのISUCON"の運営裏側を見れて、苦労、凄さなど身近な所で感じることができました。 微力ながら協力できて嬉しかったです。

この記事では、Goの参考実装からPerlへの移植をして考えたことを書きたいと思います。今後、移植をされる方の何かの参考になれば幸いです。注意として、ここでの考えは公式の見解ではなく、あくまで個人的な見解です。

できるだけGo実装に寄せる

移植は、できるだけオリジナル実装のGoに寄せるよう心がけました。 実装の乖離が大きいと競技としてフェアでない、移植ミスの際に気づきやすくなりそう、そんなことが理由です。

具体的には、次の2つを行いました。

  1. Cpanel::JSON::XS::Typeで、JSONレスポンスを明示
  2. 返り値でエラーも返してみる

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の恒例(?)でしょうか。気づいてもらえたみたいで、よかったです。

おわりに

初めての移植でしたが、本番一発勝負で受け入れてもらえるか、きちんと動作するか緊張感がありましたが、その分充実感も大きかったです。楽しかったです!ありがとうございました!

*1:ISUCON」は、LINE株式会社の商標または登録商標です。