人狼議事のプログラム負荷を減らしてみた

このサイトには広告が含まれています。

この記事は約6分で読めます。
スポンサーリンク

人狼物語とその派生の人狼議事は、負荷が高いことでその筋では有名。

この負荷をなんとかできないのか? と必要に応じてAIに相談しつついろいろ改造してみた結果、劇的に軽くなったので、やり方をシェアしてみる。

なお、使用したAIはいずれも「ユーザーが入力したコードはAIの品質向上には使わない」としているサービス。

そして改造したソースコードそのものは今のところ公開してない。Gitの扱いに慣れてなくて色々アレなリポジトリになっており……ちょっと公開しづらくて……。

なんで負荷が重いのか?

    人狼物語(人狼議事)の負荷の重さは、

    • ファイル入出力回数・データ量の多さ
    • ライブラリファイルの多さ

    に由来するらしい。

    なんでここらへんの処理が重いのかはおれも理解し切れてないんだけど、「物理的に隔たりがあるものへのアクセスは負荷が重い」と考えるといいらしい。

    プログラムはメモリ内に読み出されて実行されるので、メモリ内で済むことは低負荷。ストレージ保存・読み出しや外部サービスとの接続などになると負荷が上がるという感じで考えると、だいたい合ってるっぽい。

    別途、CPUパワーを使う処理も重いとされているが、それは人狼議事の場合は、現代のサーバーマシンだと

    • 正規表現置換

    ぐらいのようだ。

    負荷を改善する方法(ファイル入出力編)

    ファイル入出力の多さに関して負荷を下げるには、

    • データをファイル保存形式からDB保存形式に変更する
    • DBアクセスの効率を追求する

    の2点になる。

    DB保存形式に変更するには?

    かなり大変で時間がかかる。手を出すときは覚悟して取りかかってね。

    具体的にどうすればいいか? はおれが説明するより、コーディング支援AIに聞くほうが早くて正確だと思う。

    DB設計は基本的にdatalabelに従えばいいので、その点は楽だよ。すごいぜあずさん。

    readとかwriteとか書いてある処理を中心に地道にDB保存に書き換えていくことになるんだけど、いやほんとめんどくさい。大変。

    DBアクセスの効率化

    DBアクセスを効率化するには、

    • 同じクエリは何回も使わず、結果をキャッシュして使う
    • 複数のテーブルからレコードを取得するときなどは、まとめて1本のクエリで読み込んでからプログラム側でデータ処理する

    という感じになる。詳しいことはおれが語るより、その筋の知見を正しく学んだほうがいいであろう。

    readvil/writevilなどを初めとするファイル入出力をDB保存に変更した後、DBのクエリログを取って参照すると、村データとプレイヤーデータの読み込みと書き込みが意味わからんぐらい頻繁に行われていることがわかる。ヤバい。

    しかもプレイヤーデータは、1人書き込めばいいところを毎回毎回全員書き込んでたりする。ヤバい。

    (そもそもファイル入出力形式では村データとプレイヤーデータを同じファイルに保存しているので、いっそ全書き換えしたほうが低負荷!ということかもしれないが。DBに変えた際にはそうはいかない)

    読み込み・書き込み回数を最適化して適宜メモリキャッシュを利用すると、さらに負荷が下がる。

    DBアクセスに変えずに負荷を下げる案

    読み込み・書き込み回数を最適化して適宜メモリキャッシュを利用するのは、DB保存に変更しなくてもできることなので、この点だけでも見直せばかなり負荷は下がると思う。

    (ファイル入出力で1プレイヤーだけ読み書きが可能かはわからないし、それで負荷が下がるかもわからないので、そこら辺は自己責任で試してね)

    なおファイル入出力で読み込み・書き込み回数をチェックするにはaplogを徹底的に仕込むしかないと思う。それでもDB保存に書き換えるよりはよっぽど楽なので、気になる人はお試しあれ……。

    負荷を改善する方法(ライブラリファイル編)

    ライブラリファイルの多さに関しては、「必要になってからrequireする」という処理が徹底されている。だが、ここにはちょっと落とし穴があり、根本的にPerl CGIは負荷が重いという問題がある。

    Perl CGIは1アクセスごとに1つのプロセスとなり、大量のライブラリファイルを読み込んで処理を行うのだ……!!

    例えば国のトップから村に移動して、発言を送信すると、4プロセスぐらいになる。1ユーザーで。

    この問題への対処がFCGIという仕組み。

    FCGIは一度プロセスを起動すると、一定時間プロセスが生き続ける。

    その間は1回読み込んだライブラリファイルは二度と読み込まないし、複数のユーザーの処理を捌けるので、圧倒的に負荷が下がる。

    しかしPerlでFCGIが利用できるかはレンタルサーバーの設定によるので、お使いのレンタルサーバーの設定を確認しよう。使えるサーバーならFCGIに移行チャレンジしてみるのもあり。

    ちなみにシンレンタルサーバー・エックスサーバーではFCGIが使える。特にエックスサーバーは値段に対するサーバースペックと安定性が神がかっててオススメ

    PR

    FCGIに移行を試すときの注意点

    とはいっても人狼議事をFCGIに移行するのはこれまた大変である。

    実際に遭遇した特に大変だった注意点は、

    • FCGIのデメリットであるグローバル変数汚染対策として全ライブラリファイルにuse strictを書き込んだら、大量の未定義値エラーが出て修正に一苦労
      • しかもまだ根絶できてない
    • 見物人のROLEIDが999のため、大量の未定義ROLEIDが発生してヤバいことになる

    ここら辺。めんどかった。

    見物人は独自のハッシュリストを作って対応したけど、これも結構めんどい書き換えなので、やる人は頑張ってね……。

    負荷を改善する方法(正規表現編)

    ランダム発言の処理だったり、アンカー展開だったり、正規表現はあっちこっちで使われているんだけれども。

    この正規表現ってやつは、

    • マッチ対象を探す処理の負荷が高い
    • ひとつもマッチしないほうが高負荷

    という厄介な性質がある。それに加えて元々のソースコードでは正規表現は無条件実行になっている。

    この負荷を少しでも下げるには、「正規表現で置換する対象が発言内に含まれているかどうか」をindexとかで事前チェックするといいそうだ。

    indexの軽い処理で文字列内に置換対象があるとわかったら、改めて正規表現処理を行う。なかったらスルーする、という形。

    例えばCvtRandomTextの最初のほうにこう書くと

    return $mes if ( index( $mes, '[[' ) < 0 );

    「[[」(ランダムでよく使われる2重カッコ)が発言中になかったらランダム構文チェックを全部行わずに済ませられる。

    個別の正規表現処理にも1つずつ事前チェックを入れてくと更に負荷が下がる。

    内部処理をあれこれいじり倒した感想

    こうして内部処理をあれこれいじり倒した感想は、「あずさんって恐ろしく優秀なPerlプログラマーだったんだな……」だった。

    ご本人も仰る通りで負荷への配慮はほとんどされていないんだけれども、その他の面に関しては本当にすごい。

    丁寧なライブラリ分け&パッケージ化、デバッグログの出力や不正クエリの除外、グローバル変数を全く使わず必要最低限のrequireで済ませる処理形式、拡張性豊かな細部設計などなど。メソッドや変数の命名もわかりやすいし。

    処理内容は全てが最適ではないと思うし、ブログでも設計ミスしたと仰っていた箇所もあったと思う。けれども、独力で全てのコードを書く以外なかった時代にこの品質のプロジェクトを個人で作り上げてるのは凄まじい。仕事でもないのに。仕事でもないのに!!

    そこに様々な処理を上乗せして豊かに拡張したななころびさんもすごいよね。現代の長期人狼の基本形を作ったのがあずさんとななころびさんだと思うんだよな……。

    おわりに

    負荷を下げるヒントのまとめは

    • ファイル入出力回数を減らす
    • できるならDB保存にするとファイル入出力よりは負荷が下がる
    • FCGI化できると更に負荷が下がる
    • 正規表現は無条件にやらずに、必要なときだけやる

    こんな感じ。正直言ってものすごく大変なので、やる人は頑張って……。

    タイトルとURLをコピーしました