カテゴリー別アーカイブ: Perl

ループ展開よりルックアラウンドアサーションの方が速い場合もある

Onemanのテンプレート処理のコードの中で、HTML抽出部分の正規表現にループ展開を用いて高速化する事を課題にしていたのですが、いざやってみると、ループ展開は使わずに元のルックアラウンドアサーションのままが一番速そうだという事が分かりました。
これまでもループ展開の方が絶対に速いと思い込みベンチマークも取っていなかったのですが、分からないものですね。恐るべしオプティマイザ。

やりたかった事

Onemanのテンプレート処理部分をなるべく速いコードに置き換える事。

Onemanはテンプレート処理の最初のフェーズでソースのHTML部分をPerlコードに変換します。具体的には、以下のようなテンプレートの%>と<%で囲まれた太字部分を抽出して、各種エスケープと出力処理に置き換えます。

%><table>
  <caption>環境変数一覧</caption>
  <tbody><%
    for my $key ( sort keys %ENV ) { %>
        <tr>
          <th><%= $key %></th>
          <td><%= $ENV{$key} %></th>
        </tr><%
    } %>
  </tbody>
</table><%

抽出するためのオリジナルの正規表現は以下です。.+?によるマッチの前後にルックアラウンドアサーションで%>と<%を除外するというシンプルなコードでした。

(?<= %> ) (?! <% ) (.+?) (?= <% )

これを、高速化のためにループ展開へ置き換える、というのがやりたかった事です。

試した内容

おそらく最も最適化できていて、タグを優先するか否かが異なる2パターンのループ展開(221、222)とそれに至るまでの計8パターンの正規表現を作成し、オリジナルと比較してみました。

use Benchmark qw( cmpthese );

my $HTML = '%>' . ( << 'END_OF_HTML' x 10 ) . '<%';
    <table>
      <tbody><%
        for my $key ( sort keys %ENV ) { %>
            <tr>
              <th><%= $key %></th>
              <td><%= $ENV{$key} %></td>
            </tr><%
        } %>
      </tbody>
    </table>
END_OF_HTML

cmpthese( 10000, {
    Original => sub { $HTML =~ s{ (?<= %> )
        (?! <% ) (.+?) (?= <% )
    }{$1}gxms; },

    111 => sub { $HTML =~ s{ (?<= %> )
        ( (?: [^<]+ | (?: < (?! % ) ) )+ )
    }{$1}gxms; },

    112 => sub { $HTML =~ s{ (?<= %> )
        ( (?: (?: < (?! % ) | [^<]+ ) )+ )
    }{$1}gxms; },

    121 => sub { $HTML =~ s{ (?<= %> )
        ( (?: [^<]+ | (?: < (?! % ) [^<]* ) )+ )
    }{$1}gxms; },

    122 => sub { $HTML =~ s{ (?<= %> )
        ( (?: (?: < (?! % ) [^<]* | [^<]+ ) )+ )
    }{$1}gxms; },

    211 => sub { $HTML =~ s{ (?<= %> )
        ( (?: [^<]+ | < (?! % ) ) (?: < (?! % ) [^<]* )* )
    }{$1}gxms; },

    212 => sub { $HTML =~ s{ (?<= %> )
        ( (?: < (?! % ) | [^<]+ ) (?: < (?! % ) [^<]* )* )
    }{$1}gxms; },

    221 => sub { $HTML =~ s{ (?<= %> )
        ( (?: [^<]+ | < (?! % ) [^<]* ) (?: < (?! % ) [^<]* )* )
    }{$1}gxms; },

    222 => sub { $HTML =~ s{ (?<= %> )
        ( (?: < (?! % ) [^<]* | [^<]+ ) (?: < (?! % ) [^<]* )* )
    }{$1}gxms; },
} );

221と222はOriginalより圧倒的に速い、と思ったのですが。

結果

Originalが最も高速でした。

           Rate 211 212 112 111 121 122 221 222 Original
211      2323/s  -- -1% -7% -8%-12%-14%-18%-18%     -27%
212      2357/s  1%  -- -5% -6%-10%-13%-17%-17%     -26%
112      2484/s  7%  5%  -- -1% -5% -9%-12%-13%     -22%
111      2514/s  8%  7%  1%  -- -4% -7%-11%-12%     -21%
121      2627/s 13% 11%  6%  4%  -- -3% -7% -8%     -17%
122      2716/s 17% 15%  9%  8%  3%  -- -4% -5%     -14%
221      2824/s 22% 20% 14% 12%  8%  4%  -- -1%     -11%
222      2848/s 23% 21% 15% 13%  8%  5%  1%  --     -10%
Original 3174/s 37% 35% 28% 26% 21% 17% 12% 11%       --

ループ展開のパターンの中では予想通り22○が最も速く、また、○○1と○○2のどちらが良いかはHTMLの書き方に依りそうですが、いずれにしてもOriginalが速いのであれば難しく考える必要はありませんね。

[^<]*および[^<]+にマッチする部分が長い場合はループ展開の方が速いかな、と、タグ文字が無い部分を増やしたHTMLでも試してみましたが、結果は同じどころかOriginalがより速くなりました。もうよく分かりません。。

Oneman 0.27 – テンプレートの一括コンパイル機能

Oneman 0.27を公開しました。

今回の変更はPSGIサーバではなくWAFの方がメインで、テンプレートの一括コンパイル機能を追加しています。

use FindBin qw( $Bin );
require MyWebApp;

my $psgi_app = MyWebApp->psgi_app(
    template_dir => "$Bin/templates",
    precompile => qr/ .html z/xms,
);

template_dirで指定したディレクトリ(指定していない場合はカレントディレクトリ)配下をFile::Findを使ってサブディレクトリも含めて辿り、precompileで指定した正規表現にマッチするパスのファイルを事前にコンパイルします。

通常は、ユーザからのリクエストを受け付けてWebアプリが動いてから、必要になった時に初めてコンパイルされます。マルチプロセスのサーバの場合、このコンパイルはforkされてからの処理になるためメモリ効率が良くありません。

precompileオプションを使い、PSGIアプリケーションをコンパイルする時点でテンプレートもあらかじめコンパイルしておく事で、COWの効果を得る事ができます。

また、PSGIではなくmod_perlの環境では、cache()メソッドを使う事でスマートに書く事ができます。

まずstartup.plに以下のように書きます。

use FindBin qw( $Bin );

require MyWebApp;

MyWebApp->build(
    template_dir => "$Bin/templates",
    precompile => qr/ .html z/xms,
)->cache;

cache()メソッドにより、Onemanのメモリ空間にアプリケーションがキャッシュされます。

次にModPerl::Registryで動作するCGIファイルです。

MyWebApp->cache->run;

これでキャッシュしたアプリケーションが動作します。

この機会に、Onemanアプリケーションの様々な使い方をまとめてみます。

# PSGI ファイル内で完結するアプリケーション
Oneman::psgi_app( sub { ... }, ... );

# CGI ファイル内で完結するアプリケーション
Oneman::run( sub { ... }, ... );

# .pl ファイル内で完結するアプリケーションサーバ
Oneman::psgi_app( sub { ... }, ... )->start(...);
or
Oneman::build( sub { ... }, ... )->start(...); # build は psgi_app のエイリアス

# Oneman を継承したアプリケーションモジュールの $psgi_app
MyWebApp->psgi_app(...);

# Oneman を継承したアプリケーションモジュールの CGI 実行
MyWebApp->run(...);

# Oneman を継承したアプリケーションモジュールをサーバとして実行
MyWebApp->psgi_app(...)->start(...);
or
MyWebApp->build(...)->start(...);

# Oneman を継承したアプリケーションモジュールをキャッシュしてから実行
MyWebApp->build(...)->cache; # キャッシュ
MyWebApp->cache->run(...); # 実行

# Oneman の一部機能(テンプレート処理機能等)だけを使いたい場合
my $oneman = Oneman->core(...);
print $oneman->get_content(...);

ソース

http://www.tamashiro.org/src/Oneman-0.27/
http://www.tamashiro.org/src/Oneman-0.27.tar.gz

Oneman 0.26 – 劇的に速度向上、というかこれまでが遅すぎた

Oneman 0.22で触れたとおり、OnemanはKeep-Aliveの実現のためにselectと1バイト取得の連続でリクエストヘッダの読み込みを行っていたため、ヘッダサイズが大きい場合の速度低下が顕著でした。
でもよくよく考えてみると、Oneman 0.21で実装した入力ストリームのバッファリングをリクエスト処理全体で行えば、リクエストラインとヘッダを取得した際にバッファリングしたリクエストボディやパイプラインリクエストを、後の処理に渡せる事に気付きました。気付くのが遅い。。

そこで、Oneman 0.26では入力ストリームを64KiBずつバッファリングするように変更しました。その結果、速度が劇的に向上しています。

Oneman 0.25 (Keep-Alive)
    Requests per second:    812.36 [#/sec] (mean)

Oneman 0.26 (Keep-Alive)
    Requests per second:    2402.44 [#/sec] (mean)

Starman 0.3008 (Keep-Alive)
    Requests per second:    2412.10 [#/sec] (mean)

Oneman 0.25 (No Keep-Alive)
    Requests per second:    745.69 [#/sec] (mean)

Oneman 0.26 (No Keep-Alive)
    Requests per second:    1325.19 [#/sec] (mean)

Starman 0.3008 (No Keep-Alive)
    Requests per second:    797.39 [#/sec] (mean)

※いずれも、5回×3セット=15回中の最高値を抜粋しています。

このベンチマークはOneman 0.22の時と同様ですが、Keep-Aliveの扱いにミスがあったので、今回は一部を変更しての実施です。

当時Keep-Aliveを有効にするために、Apache Benchにオプション-kを指定していたのですが、あらためて確認すると、Apache Benchの結果がKeep-Alive requests: 0となっていました。なぜKeep-Aliveが無効になっていたか?
確認したところ、まずStarmanは、出力がストリームになっていたためでした。Apache BenchはHTTP/1.0でリクエストを行うため、サーバはchunkedエンコーディングが使用できません。Hello.psgiはContent-Lengthヘッダを持たないため、chunkedエンコーディングが使用できないStarmanはストリームで出力した上でコネクションを切断していました。
次にOnemanは、当時のバージョン0.22ではConnectionヘッダを出力していなかったためでした。Apache Benchは、レスポンスヘッダにConnectionヘッダによるKeep-Aliveの指定が無い場合、コネクションを切断する仕様のようです。バージョン0.25でConnectionヘッダを付加するよう変更したため、現在はApache BenchのKeep-Aliveに対応できています。

ベンチマークに使用するHello.psgiには、StarmanにKeep-Aliveを行わせるためにContent-Lengthヘッダを追加しました。

my $app = sub {
    my $env = shift;
    return [
        200,
        [ 'Content-Type' => 'text/plain', 'Content-Length' => 11 ],
        [ "Hello World" ],
    ];
};

前回と同様に、OnemanとStarmanをそれぞれ以下のように実行して、Apache Benchにより秒間リクエスト処理数を計測します。

$ perl -MOneman -e 'oneman "Hello.psgi", workers => 10'
$ starman --workers 10 Hello.psgi

OnemanもしくはStarmanと、Apache Benchは、いずれもLet’snote CF-R8 (Windows 7) のVirtualBoxで動作するUbuntu上で実行します。Perlは5.16.2、Apache Benchのオプションは以下のとおり。

$ ab -c 10 -t 1 -k http://127.0.0.1:5000/

OnemanとStarmanを交互に3回実行して、それぞれでabを5回ずつ実行、計30回のベンチマークを取りました。
※抜粋のみで詳細の結果は載せていませんが、Onemanの前バージョンとの比較のためバージョン0.25も同様に計測しました。

Oneman 0.26 (Keep-Alive)
    Requests per second:    1730.11 [#/sec] (mean)
    Requests per second:    2344.59 [#/sec] (mean)
    Requests per second:    2368.74 [#/sec] (mean)
    Requests per second:    2316.48 [#/sec] (mean)
    Requests per second:    2391.14 [#/sec] (mean)

    Requests per second:    1700.56 [#/sec] (mean)
    Requests per second:    2317.97 [#/sec] (mean)
    Requests per second:    2358.85 [#/sec] (mean)
    Requests per second:    2402.44 [#/sec] (mean)
    Requests per second:    2366.34 [#/sec] (mean)

    Requests per second:    1803.13 [#/sec] (mean)
    Requests per second:    2362.14 [#/sec] (mean)
    Requests per second:    2322.59 [#/sec] (mean)
    Requests per second:    2381.22 [#/sec] (mean)
    Requests per second:    2377.59 [#/sec] (mean)

Starman 0.3008 (Keep-Alive)
    Requests per second:    1736.05 [#/sec] (mean)
    Requests per second:    2375.56 [#/sec] (mean)
    Requests per second:    2406.15 [#/sec] (mean)
    Requests per second:    2412.10 [#/sec] (mean)
    Requests per second:    2392.75 [#/sec] (mean)

    Requests per second:    1693.59 [#/sec] (mean)
    Requests per second:    2352.28 [#/sec] (mean)
    Requests per second:    2369.86 [#/sec] (mean)
    Requests per second:    2382.01 [#/sec] (mean)
    Requests per second:    2287.22 [#/sec] (mean)

    Requests per second:    1783.65 [#/sec] (mean)
    Requests per second:    2374.16 [#/sec] (mean)
    Requests per second:    2405.08 [#/sec] (mean)
    Requests per second:    2377.22 [#/sec] (mean)
    Requests per second:    2337.31 [#/sec] (mean)

また、Keep-Aliveを使用しない場合のベンチマークも取りました。結果は以下のとおりです。

$ ab -c 10 -t 1 http://127.0.0.1:5000/
Oneman 0.26 (No Keep-Alive)
    Requests per second:    970.57 [#/sec] (mean)
    Requests per second:    1258.08 [#/sec] (mean)
    Requests per second:    1299.39 [#/sec] (mean)
    Requests per second:    1319.84 [#/sec] (mean)
    Requests per second:    1273.80 [#/sec] (mean)

    Requests per second:    930.12 [#/sec] (mean)
    Requests per second:    1307.79 [#/sec] (mean)
    Requests per second:    1313.25 [#/sec] (mean)
    Requests per second:    1325.19 [#/sec] (mean)
    Requests per second:    1279.76 [#/sec] (mean)

    Requests per second:    977.43 [#/sec] (mean)
    Requests per second:    1289.74 [#/sec] (mean)
    Requests per second:    1297.43 [#/sec] (mean)
    Requests per second:    1308.71 [#/sec] (mean)
    Requests per second:    1276.44 [#/sec] (mean)

Starman 0.3008 (No Keep-Alive)
    Requests per second:    616.22 [#/sec] (mean)
    Requests per second:    769.84 [#/sec] (mean)
    Requests per second:    797.39 [#/sec] (mean)
    Requests per second:    756.97 [#/sec] (mean)
    Requests per second:    759.92 [#/sec] (mean)

    Requests per second:    539.82 [#/sec] (mean)
    Requests per second:    736.61 [#/sec] (mean)
    Requests per second:    707.97 [#/sec] (mean)
    Requests per second:    681.39 [#/sec] (mean)
    Requests per second:    728.07 [#/sec] (mean)

    Requests per second:    491.34 [#/sec] (mean)
    Requests per second:    584.63 [#/sec] (mean)
    Requests per second:    668.69 [#/sec] (mean)
    Requests per second:    668.10 [#/sec] (mean)
    Requests per second:    698.69 [#/sec] (mean)

ソース

http://www.tamashiro.org/src/Oneman-0.26/
http://www.tamashiro.org/src/Oneman-0.26.tar.gz

CIW Perl Specialistを受験しました

勤めている会社の育成の取り組みで、担当全員何らかの資格取得を目指しましょう、というのがあったので、長年書いているPerlの実力を測るべく、CIW Perl Specialistに挑戦しました。(実力、とか言いながら、実費を無駄にはできないので少し勉強しましたが。。)

結果は、得点: 98%で合格。問題出題が50問なので、1問間違えたかな?残念。

試験コンテンツに関する情報は規約上配布・公開できない事になっているので、何の参考にもならない記事ですが、勉強した内容だけ紹介します。

勉強に使ったのは、6, 7年前に購入していたラクダ本入門 Perl DBIです。公式テキストは使用しませんでした。見たかったですがけっこうお高いので。。

(ラクダ本はVOLUME1, 2あるので、)これら3冊をとりあえずざーっと読み、おもな出題項目にある関数・機能について、それらの仕様を覚えこみました。
もう10年近く書いているので普段マニュアル等を見る事はほとんどありませんが、こうやって全体を体系的に見直すと、まだ知らない事とか間違って理解していた事がいろいろ見つかりますね。

特にデバッガなんて1度も使った事が無かったので、実機で触りながら覚えました。あまりに機能が多く、しっかり覚えられたのは、ブレークポイントを設定して実行して止めて変数を確認する、ぐらいかなぁ。
特殊変数もこれを機に全部覚えてしまおう、と思いましたが、なかなか難しいですね。逆に、出題項目に含まれない言語の歴史・背景等の情報やDBI以外のモジュールについては飛ばしました。

StreamBuffer – レガシーなプロセス間非同期通信バッファ

きっとよくある車輪をまたまた再発明してしまったかも。あるプロセスが連続的に出力するデータをバッファリングして、別のプロセスがそのデータを連続的に取得するためのモジュール。

StreamBuffer-0.01 [Download] [Browse]

説明

  • レガシーなファイルベースI/Oで、Windowsでも使用可能
  • バッファリング用ファイルは一定サイズ(デフォルトは1MiB)
  • 最大約86EiB(ポインタが10進数20桁なだけで、実際には使用するPerlが扱える最大整数値が限界)のストリームデータをファイルに上書きしながら記録(そのため、ファイルサイズを超えるデータを1度に書き込んだ場合、複数回の書き込みにより更新データがファイルサイズを超えてから読み込む場合は、エラーが発生)
  • データの取得はブロッキング(と言っても、定期的な更新確認による疑似ブロッキング)とノンブロッキングに対応していて、ブロッキング中は1秒毎のコールバックも可能
StreamBuffer::Writer->write
↓
バッファリング用ファイル
↓
StreamBuffer::Reader->read もしくは ->read_nb ※ノンブロッキング

メソッド

StreamBuffer::Writer

new
write

StreamBuffer::Reader

new
read
read_nb

共通

path
pos
close

Strawberry Perl PortableZIP editionをファイルサーバに置いて使用する

Strawberry Perlは、Windows用Perlの実装の1つです。リリースには複数の種類があり、「PortableZIP」というエディションも用意されています。これにはStrawberry Perlの関連ファイルがまとめられており、PC上に展開するだけで、インストールする事無く実行する事ができます。

そして、この展開したファイル群をファイルサーバに置いても実行させる事ができるので、(信頼されたネットワーク内に限るべき、ですが、)ファイルサーバにアクセスできるWindows PCであれば、事前に何の準備もする事なく、Perlスクリプトを実行できます。

例えば、拙作のWAF+PSGI serverのOneman、OnemanベースのWebアプリと合わせてファイルサーバに置く事で、ネットワーク内の全てのPC上でWebアプリを動作させる事ができます。

ファイルサーバへの配置

例として、以下のように、Strawberry Perl、Oneman、Webアプリモジュール、Webアプリ実行用Perlスクリプト、Perl実行用バッチファイルを配置します。
32bit用だけを置いておけば64bit PCでも動作しますが、ここでは環境変数を参照して、実行するプログラムを振り分けてみます。

\\Server\share\strawberry-perl-5.16.2.1-32bit-portable\
\\Server\share\strawberry-perl-5.16.2.1-64bit-portable\
\\Server\share\Oneman.pm
\\Server\share\MyWebApp.pm
\\Server\share\mywebapp.pl
\\Server\share\mywebapp.bat

MyWebApp.pm

Oneman WAFを使用したWebアプリケーションを作成します。
例としてHello, world!を表示するだけのアプリですが、次に作成するmywebapp.plから$Binを渡す等でファイルサーバのパスが取得できるので、ファイルサーバに対するファイルI/Oを行う事もできます。
もしくは$ENV{USERPROFILE}等を参照して、ローカルPCにファイルを保存してもよいかもしれません。

# MyWebApp.pm
package MyWebApp;
use base 'Oneman';
sub app {
    my ($self) = @_;
    $self->write('Hello, world!');
    return;
}
1;

mywebapp.pl

MyWebAppを動作させるためのスクリプトを作成します。
OnemanはPSGIサーバも内蔵しているため、$psgi_appからstart()メソッドを実行する事によりローカルPCでWebサーバが起動します。
start()メソッド(実体はOneman::oneman())のpreforkオプションを使い、ソケットをオープンした後、BUILDERとWORKERをforkする前に、ブラウザを起動させる処理を加えています。
また、jQuery等の静的ファイルも合わせて配置し、staticオプションにより出力させる事もできます。

# mywebapp.pl
use FindBin qw( $Bin );
use lib $Bin;
require MyWebApp;
MyWebApp->psgi_app->start(
    prefork => sub { system('START http://localhost:5000/') == 0 },
);

mywebapp.bat

mywebapp.plを実行するためのバッチファイルを作成します。
環境変数によりOSが32bitか64bitかを判別して実行ファイルを振り分けます。決定したperl.exeによりmywebapp.plを実行します。

# mywebapp.bat
@ECHO OFF

IF %PROCESSOR_ARCHITECTURE% == "x64" (
    SET PERL=\\Server\share\strawberry-perl-5.16.2.1-64bit-portableperl\bin\perl.exe
) ELSE (
    SET PERL=\\Server\share\strawberry-perl-5.16.2.1-32bit-portableperl\bin\perl.exe
)

%PERL% \\Server\share\mywebapp.pl

実行

以下のコマンドをローカルPCのコマンド プロンプトから実行します。

>\\Server\share\mywebapp.bat

もしくは、バッチファイルなのでショートカット (.lnk) を作成しておくと便利です。

Webサーバが起動した後に、自動的にブラウザが起動して、Webアプリのトップページが表示されます。

まとめ

このように、Strawberry Perl PortableZIP editionをファイルサーバに展開して使用すれば、特別にWebサーバを用意する事なく、複数のPCでPerlベースのWebアプリを動作させる事ができます。

ただし、ネットワーク上に置いたファイルをローカルPCで実行する事になるので、セキュリティポリシーによっては正常に動作しないかもしれません。また、動作する場合においても、それらファイルが悪意あるユーザから不正に書き換えられないようにするため、一般ユーザには読み込みのみが可能になるよう管理者が配置する、等の工夫が必要です。

Oneman 0.22 – Starmanに肉薄する速さ YMMV ;-)、ほんとか??

細かな修正を行い、Oneman 0.22を公開しました。

さて、Onemanの機能数や品質は低く、Starmanには到底及びませんが、さて速さはどんなものだろうと思い、試してみました。

ベンチマークに使用したのはHello.psgi。

my $app = sub {
    my $env = shift;
    return [
        200,
        [ 'Content-Type' => 'text/plain' ],
        [ "Hello World" ],
    ];
};

StarmanとOnemanをそれぞれ以下のように実行して、ab (Apache HTTP server benchmarking tool) により秒間リクエスト処理数を計測します。

$ starman --workers 10 Hello.psgi
$ perl -MOneman -e 'oneman "Hello.psgi", workers => 10'

StarmanもしくはOnemanと、abは、いずれもLet’snote CF-R8 (Windows 7) のVirtualBoxで動作するUbuntu上で実行します。Perlは5.16.2、abのオプションは以下のとおり。

$ ab -c 10 -t 1 -k http://127.0.0.1:5000/

StarmanとOnemanを交互に3回実行して、それぞれでabを5回ずつ実行、計30回のベンチマークを取りました。さてその結果は?

やっぱりStarmanは速いですが、Onemanも意外に健闘しています。

Starman 0.3006
    Requests per second:    544.31 [#/sec] (mean)
    Requests per second:    616.26 [#/sec] (mean)
    Requests per second:    635.29 [#/sec] (mean)
    Requests per second:    682.02 [#/sec] (mean)
    Requests per second:    639.67 [#/sec] (mean)

    Requests per second:    591.27 [#/sec] (mean)
    Requests per second:    788.01 [#/sec] (mean)
    Requests per second:    728.52 [#/sec] (mean)
    Requests per second:    746.70 [#/sec] (mean)
    Requests per second:    727.46 [#/sec] (mean)

    Requests per second:    608.93 [#/sec] (mean)
    Requests per second:    705.51 [#/sec] (mean)
    Requests per second:    746.26 [#/sec] (mean)
    Requests per second:    739.99 [#/sec] (mean)
    Requests per second:    729.13 [#/sec] (mean)

Oneman 0.22
    Requests per second:    540.42 [#/sec] (mean)
    Requests per second:    648.77 [#/sec] (mean)
    Requests per second:    609.61 [#/sec] (mean)
    Requests per second:    637.44 [#/sec] (mean)
    Requests per second:    615.70 [#/sec] (mean)

    Requests per second:    443.15 [#/sec] (mean)
    Requests per second:    640.58 [#/sec] (mean)
    Requests per second:    625.77 [#/sec] (mean)
    Requests per second:    656.86 [#/sec] (mean)
    Requests per second:    639.11 [#/sec] (mean)

    Requests per second:    517.57 [#/sec] (mean)
    Requests per second:    638.55 [#/sec] (mean)
    Requests per second:    626.50 [#/sec] (mean)
    Requests per second:    634.62 [#/sec] (mean)
    Requests per second:    645.03 [#/sec] (mean)

特定の環境でのテストではありますが、Onemanも実用に十分耐える速さと言えそうです。ちょっと嬉しい。
しかし、仮想環境でのベンチマークとは言え、StarmanのPODに載せられている結果と1桁違うのがなんとも。。

また、Starmanには、

High Performance
Uses the fast XS/C HTTP header parser

とあります。
Onemanはpure perlである事に加え、Keep-Aliveの実現と、(alarmを使えない)Windows上で実行時のタイムアウト処理のために、selectと1バイト取得の連続でヘッダの読み込みを行っているので、効率がかなり悪いです。
そのため、リクエストヘッダのサイズが大きくなると、Starmanとの差はもっと大きくなるかもしれません。

ソース

http://www.tamashiro.org/src/Oneman-0.22/
http://www.tamashiro.org/src/Oneman-0.22.tar.gz

Oneman 0.21 – PSGI版Movable Typeを動かせるようにしました

先日公開した自作のPSGIサーバOnemanは、Plack::Test::SuiteはPASSしたものの、巷のPSGIアプリケーションがきちんと動くのか?
そこで、PSGI対応したMovable Typeの動作確認をしてみました。

>perl -MOneman -e "oneman 'mt.psgi', workers => 10"
Oneman: accepting connections at http://0.0.0.0:5000/

ブラウザでアクセスしてみると、、動かない。。
トップページはきちんと表示され、おぉ、自分で作ったサーバでMTが動いている!という少しの感動があったのですが、まず静的コンテンツの取得が遅く、次のページにも遷移できない。

早速調査を行い、大きく2点の修正をして、Movable Typeが正常かつ軽快に動作するようになりました。これで多少は使えるサーバになったかな。

以下に、その修正点について書いています。

ハンドルからの入力を固定長化

PSGIアプリからのレスポンスがhandleの場合、getline()で処理をする前に、入力を64KBytesに固定長化するよう変更しました。

local $/ = 65536;

これを忘れていたためにgetline()で行毎に読み込みしていたため、jquery-ui.jsのような巨大なテキストファイルのレスポンスに時間を要していました。
固定長化した事で劇的に改善しました。64KBytesという値はPlack::Utilを参考にしています。

入力ストリームのバッファリング

ブラウザからのGETリクエストはうまく動作するのですが、POSTリクエストに反応しないため、MT::PSGIのソースを読んでみると、以下のコードがありました。

        syswrite $child_in, do {
            local $/;
            my $fh = $env->{'psgi.input'};
            <$fh>;
        };

クライアントが送信するデータをEOFまで読み込みさせています。なんと乱暴な!これではKeep-AliveなHTTP/1.1クライアントではアクセスできなくて当然ではないか、と思ったのですが、でもplackupやstarmanで試してみると正しく動作する。。

HTTP::Server::PSGIのソースを読んでみて分かったのですが、PSGIサーバ側でいったん入力をバッファリングしてあげるんですね。Oneman 0.20では、クライアントのソケットをそのままpsgi.inputに入れていたため、そこを修正して入力をバッファリングするようにしました。

これで無事Movable Typeが動作するようになりました。さて、次は何を動かしてみようかな。

ソース

http://www.tamashiro.org/src/Oneman-0.21/
http://www.tamashiro.org/src/Oneman-0.21.tar.gz

Oneman – A simple web application framework with PSGI server

Windowsでも動作するpreforking PSGI server「Oneman」を作ってみました。5.8.1以上のコアモジュールのみで動作する1ファイル構成のPerlモジュールです。つまり、1ファイル置くだけで動くPSGIサーバです。

HTTP/1.1の一部(Chunked Transfer Coding、Persistent ConnectionsいわゆるKeep-Alive、他)に対応していて、Plack::Test::SuiteをPASSしています。preforkingなマルチプロセスサーバなので、ブロッキングが発生するリクエストの処理中にも別のリクエストを受け付ける事ができます。

使い方

「oneman()」という関数を実行する事でOneman PSGIサーバが動作します。oneman()を実行するスクリプトのコマンド引数にPSGIファイルを指定すると、デフォルトのパラメータでPSGIサーバが起動します。

>perl -MOneman -e oneman mywebapp.psgi
Oneman: accepting connections at http://0.0.0.0:5000/

詳しい使い方はドキュメントをご覧ください。

作成の目的と経緯

Oneman PSGIサーバは、とある方から「ブラウザで動作するターミナルソフトを作って欲しい」という依頼をきっかけに作成しました。

具体的には、Windows PCのシリアルポートに接続されたマイコンから受信したデータを、Ajaxを用いてブラウザに出力する、という動作が要件です。シリアルポートの接続には プロセスを常駐させる必要があり、またデータの受信をAnyEvent等で対応する事が難しくマルチプロセスで処理したかったのですが、そのようなWebサーバモジュールが見つかりませんでした。だからといってApacheを使用するのも大げさなので、要件を満たすサーバを作ってみた、というのが経緯です。昨今の状況に合わせてPSGIサーバとして実装しました。

当初はHTTP/1.0のみに対応した今よりもさらに簡素なサーバでしたが、ブラウザから大量のAjaxリクエストを実行すると数百回~数千回のところでエラーが発生してしまい、その原因・対策も見つけられませんでした。 netstatで確認すると大量のセッションが残っていたため、Keep-Aliveが必要かなと思い、HTTP/1.1に対応させる事にして今の形になっています。

このように単一クライアント向けWindowsソフトウェアへの組み込み用途で作成しており、インターネットに公開する不特定多数クライアント向けサーバとしての使用はあまり想定していません。セキュリティにはある程度気をつけており、Ubuntu、CentOS、Mac OS X等でも正常に動作する事を確認していますが、もしご利用いただく場合はご自身の責任の範囲でお願いいたします。

興味のあった無停止更新のためのプロセス構造等も試してみたり、良い勉強になっています。

web application framework?

実はPSGIサーバは後付けの機能で、Onemanは元々ChatPromptのために作成したCGI/PSGI対応の簡易なWebアプリケーションフレームワークです。Perlコードをそのまま埋め込めるテンプレート機能と基本的なパラメータハンドリングの機能を持っています。

ソース

http://www.tamashiro.org/src/Oneman-0.20/
http://www.tamashiro.org/src/Oneman-0.20.tar.gz