2009/10/02


最近熱いPSGI/Packですが、サーバのStandaloneではかなり高速なパフォーマンスが出ているらしく、試してみようと思った所、Windowsでは動かない箇所があったのでいろいろやった作業履歴。
まず、Standaloneサーバが速いと言われる理由としてsendfile(2)を使っているのですが、Windowsにはsendfile(2)がありません。しかしソケット前提であるならばTransmitFileというAPIがあります。
sendfile(2)と同じようにZeroCopyでファイルから指定ディスクリプタに流し込むAPIです。
Plackが使っているSys::Sendfileに対してこのAPIを使う様にWindowsポーティングしました。
Sys::Sendfileのauthorにもpull requestを送りました。次にPlackにてWindowsで使えないAPIを叩いている部分に対するpatchを書きました。
これで一応、高速化対応は完了。
Plackに付属の画像を返すpsgiアプリケーションを起動してベンチマークを取ってみました。
# plackup -s Standalone -a eg/dot-psgi/image.psgi
ベンチマーク結果は以下の通り。まず高速化対応前。
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        Plack-Server-Standalone/0.01
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        2397678 bytes

Concurrency Level:      1
Time taken for tests:   1.219 seconds
Complete requests:      10
Failed requests:        0
Write errors:           0
Total transferred:      23978730 bytes
HTML transferred:       23976780 bytes
Requests per second:    8.21 [#/sec] (mean)
Time per request:       121.876 [ms] (mean)
Time per request:       121.876 [ms] (mean, across all concurrent requests)
Transfer rate:          19213.49 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2   4.9      0      16
Processing:   109  119  10.9    125     141
Waiting:        0    5   7.5      0      16
Total:        109  120  12.9    125     141

Percentage of the requests served within a certain time (ms)
  50%    125
  66%    125
  75%    125
  80%    141
  90%    141
  95%    141
  98%    141
  99%    141
 100%    141 (longest request)
そして対応後 This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        Plack-Server-Standalone/0.01
Server Hostname:        localhost
Server Port:            8080

Document Path:          /
Document Length:        2397701 bytes

Concurrency Level:      1
Time taken for tests:   0.578 seconds
Complete requests:      10
Failed requests:        0
Write errors:           0
Total transferred:      23978960 bytes
HTML transferred:       23977010 bytes
Requests per second:    17.30 [#/sec] (mean)
Time per request:       57.813 [ms] (mean)
Time per request:       57.813 [ms] (mean, across all concurrent requests)
Transfer rate:          40504.51 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    3   6.6      0      16
Processing:    31   55  22.4     47      94
Waiting:        0    3   6.6      0      16
Total:         31   58  24.5     47     109

Percentage of the requests served within a certain time (ms)
  50%     47
  66%     47
  75%     63
  80%     94
  90%    109
  95%    109
  98%    109
  99%    109
 100%    109 (longest request)
だいたいRequests perl secondで2倍くらい速くなってます。すばらしい。

miyagawa++ kazuho++ Plack++
Posted at by



2009/09/30


ASINを指定すると、Amazonのアフィをヨロシク表示してくれるblosxomのプラグインでawsxomというのがあるのですが、以前それをItemSearchにも対応させ、以降何回か使ってました。ただ最近は記事を書く際に文章で頭が一杯になってしまい、毎回アフィを貼るのを忘れてしまうという難病にかかってしまったせいでawsxomをamazonの仕様変更に追従させるのを忘れてました。
で、案の定先ほどの記事をポストした際にASIN書いたら見事に記事が壊れて泣くハメに...

ええいと重い腰を上げて修正してみました。
修正方法はhail2uさんが書いた物をベースに修正しました。
あまりawxsomの原形を留めていないので修正後のファイルで...
#!/usr/bin/perl
# ---------------------------------------------------------------------
# awsxom: AWSからデータを取得して書影その他を作成(ECS v4対応版)
# Author: Fukazawa Tsuyoshi <tsuyoshi.fukazawa@gmail.com>
# Version: 2006-11-24
# http://fukaz55.main.jp/
# Modified: Yasuhiro Matsumoto <mattn.jp@gmail.com>
# ---------------------------------------------------------------------
package awsxom;

use strict;
use LWP::UserAgent;
use CGI qw/:standard/;
use FileHandle;
use URI::Escape;
use Digest::SHA::PurePerl qw(hmac_sha256_base64);

# --- Plug-in package variables --------
my $asoid = "XXXXXX-22";            # AmazonアソシエイトID
my $devkey = "XXXXXXXXXXXXXXXXXXXX";        # デベロッパートークン
my $secret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";    # シークレットキー
my $cachedir = "$blosxom::plugin_state_dir/aws_cache";  # XMLのキャッシュ用ディレクトリ
my $EXPIRE = 24 * 7;                # データを再読込する間隔(単位:時間)
my $default_template = "awsxom";        # デフォルトのテンプレートファイル名

my $VERSION = '1.4';
my $ua_name = "awsxom $VERSION";
my $endpoint = "ecs.amazonaws.jp";
my $unsafe   = "^A-Za-z0-9\-_.~";
my $debug_mode = 0;

# ---------------------------------------------------------------------
sub start {
    # キャッシュ用ディレクトリの作成
    if (!-e $cachedir) {
        my $mkdir_r = mkdir($cachedir, 0755);
        warn $mkdir_r
        ? "blosxom : aws plugin > \$cachedir, $cachedir, created.\n"
        : "blosxom : aws plugin > mkdir missed:$!";
        $mkdir_r or return 0;
    }

    1;
}

# ---------------------------------------------------------------------
sub story {
    my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
    $$body_ref = conv($$body_ref);
    1;
}

# ---------------------------------------------------------------------
sub foot {
    my($pkg, $currentdir, $foot_ref) = @_;
    $$foot_ref = conv($$foot_ref);
    1;
}

# ---------------------------------------------------------------------
sub conv {
    $_ = shift;

    # ASIN/ISBNが書かれていたら置き換える
    # テンプレート指定版
    s/(?:ASIN|ISBN):([A-Z0-9]{10}):(.*?):/to_html_asin($1,$2)/ge;

    # テンプレート無指定版
    s/(?:ASIN|ISBN):([A-Z0-9]{10})/to_html_asin($1,$default_template)/ge;

    # テンプレート無指定版
    s/(?:AWSWORD):([a-zA-Z0-9_]*?):/to_html_word($1,$default_template)/ge;

    return $_;
}

# ---------------------------------------------------------------------
# ASINからAmazonのアフィリエイト用HTMLを作成
sub to_html_asin {
    my ($asin, $template) = @_;    # ASINとテンプレ名称
    my $cache = "$cachedir/$asin.xml";
    my $outfile = "$cachedir/$asin.html";

    my $q = CGI->new;
    $q->param("AWSAccessKeyId", $devkey);
    $q->param("AssociateTag", $asoid);
    $q->param("Timestamp", sprintf("%04d-%02d-%02dT%02d:%02d:%02d.000Z", sub { ($_[5]+1900, $_[4]+1, $_[3],  $_[2], $_[1], $_[0] ) }->(gmtime(time))));
    $q->param("Service", "AWSECommerceService");
    $q->param("Operation", "ItemLookup");
    $q->param("ItemId", $asin);
    $q->param("ResponseGroup", "Medium,Offers");
    $q->param("Version", "2009-01-06");
    my @p = $q->param();
    foreach (@p) {
        $_ = escape($_) . "=" . escape($q->param($_));
    }
    my $qs = join("&", sort(@p));
    my $signature = hmac_sha256_base64("GET\n$endpoint\n/onca/xml\n$qs", $secret) . "=";
    my $url = "http://$endpoint/onca/xml?$qs&Signature=" . escape($signature);

    # 取り込み直す必要はあるか?
    if (!(-e $cache) || (-M $cache > ($EXPIRE / 24))) {
    # AWSから情報を取得してキャッシュファイルに保存
        # UserAgent初期化
        my $ua = new LWP::UserAgent;
        $ua->agent($ua_name);
        $ua->timeout(60);
        my $rtn = $ua->mirror($url, $cache);
    }

    # キャッシュからXMLを読み込んで解析
    my $content = getFile($cache);
    my %detail = parseXML($content, $asin);

    # テンプレートを展開。エラーの場合はエラー文字列を返す
    my $form;
    if (!defined($detail{"ErrorMsg"})) {
        #$form = &$blosxom::template($blosxom::path, $template, 'html');
        my $fh = new FileHandle;
        if ($fh->open("< $blosxom::datadir/$template.html")) {
            $form = join '', <$fh>;
            $form =~ s/\$(\w+)/$detail{$1}/ge;
            $fh->close();
        }
    }
    else {
        $form = "<p>" . $detail{"ErrorMsg"} . "</p>";
    }

    return $form;
}

# ---------------------------------------------------------------------
# ASINからAmazonのアフィリエイト用HTMLを作成
sub to_html_word {
    my ($word, $template) = @_;    # ASINとテンプレ名称
    my $cache = "$cachedir/$word.xml";
    my $outfile = "$cachedir/$word.html";

    my $q = CGI->new;
    $q->param("AWSAccessKeyId", $devkey);
    $q->param("AssociateTag", $asoid);
    $q->param("Timestamp", sprintf("%04d-%02d-%02dT%02d:%02d:%02d.000Z", sub { ($_[5]+1900, $_[4]+1, $_[3],  $_[2], $_[1], $_[0] ) }->(gmtime(time))));
    $q->param("Service", "AWSECommerceService");
    $q->param("Operation", "ItemSearch");
    $q->param("Keywords", $word);
    $q->param("SearchIndex", "Books");
    $q->param("ResponseGroup", "Medium,Offers");
    $q->param("Version", "2009-01-06");
    my @p = $q->param();
    foreach (@p) {
        $_ = escape($_) . "=" . escape($q->param($_));
    }
    my $qs = join("&", sort(@p));
    my $signature = hmac_sha256_base64("GET\n$endpoint\n/onca/xml\n$qs", $secret) . "=";
    my $url = "http://$endpoint/onca/xml?$qs&Signature=" . escape($signature);

    # 取り込み直す必要はあるか?
    if (!(-e $cache) || (-M $cache > ($EXPIRE / 24))) {
    # AWSから情報を取得してキャッシュファイルに保存
        # UserAgent初期化
        my $ua = new LWP::UserAgent;
        $ua->agent($ua_name);
        $ua->timeout(60);
        my $rtn = $ua->mirror($url, $cache);
    }

    # キャッシュからXMLを読み込んで解析
    my $content = getFile($cache);
    $content =~ s!.*?(<Item>.*?</Item>).*!$1!is;
    my $asin = "";
    $asin = $1 if ($content =~ /<ASIN>([^<]*)<\/ASIN>/);
    return "" if !$asin;
    my %detail = parseXML($content, $asin);

    # テンプレートを展開。エラーの場合はエラー文字列を返す
    my $form;
    if (!defined($detail{"ErrorMsg"})) {
        #$form = &$blosxom::template($blosxom::path, $template, 'html');
        my $fh = new FileHandle;
        if ($fh->open("< $blosxom::datadir/$template.html")) {
            $form = join '', <$fh>;
            $form =~ s/\$(\w+)/$detail{$1}/ge;
            $fh->close();
        }
    }
    else {
        $form = "<p>" . $detail{"ErrorMsg"} . "</p>";
    }

    return $form;
}

# ---------------------------------------------------------------------
# ファイルを読み込む
sub getFile {
    my $cache = shift;
    my $fh = new FileHandle;

    $fh->open($cache);
    my @data = <$fh>;
    $fh->close();
    my $content = join('', @data);
    return undef if (!$content);

    return $content;
}

# ---------------------------------------------------------------------
sub parseXML {
    my ($buf, $asin) = @_;
    my %detail;

    # Amazonへのリンク
    $detail{"Link"} = "http://www.amazon.co.jp/exec/obidos/ASIN/$asin/ref=nosim/$asoid";

    # 個々の要素の抽出
    $detail{"Asin"} = $1 if ($buf =~ /<ASIN>([^<]*)<\/ASIN>/);
    $detail{"ProductName"} = $1 if ($buf =~ /<Title>([^<]*)<\/Title>/);
    $detail{"Catalog"} = $1 if ($buf =~ /<Binding>([^<]*)<\/Binding>/);
    $detail{"ReleaseDate"} = $1 if ($buf =~ /<PublicationDate>([^<]*)<\/PublicationDate>/);
    $detail{"ReleaseDate"} = $1 if ($buf =~ /<ReleaseDate>([^<]*)<\/ReleaseDate>/);
    $detail{"Manufacturer"} = $1 if ($buf =~ /<Manufacturer>([^<]*)<\/Manufacturer>/);
    $detail{"ImageUrlSmall"} = $1 if ($buf =~ /<SmallImage>[^<]*?<URL>([^<]*)<\/URL>/);
    $detail{"ImageUrlMedium"} = $1 if ($buf =~ /<MediumImage>[^<]*?<URL>([^<]*)<\/URL>/);
    $detail{"ImageUrlLarge"} = $1 if ($buf =~ /<LargeImage>[^<]*?<URL>([^<]*)<\/URL>/);
    $detail{"Availability"} = $1 if ($buf =~ /<Availability>([^<]*)<\/Availability>/);
    $detail{"ListPrice"} = $1 if ($buf =~ /<LowestNewPrice>.*?<FormattedPrice>([^<]*)<\/FormattedPrice>/);
    $detail{"OurPrice"} = $1 if ($buf =~ /<ListPrice>.*?<FormattedPrice>([^<]*)<\/FormattedPrice>/);
    $detail{"UsedPrice"} = $1 if ($buf =~ /<LowestUsedPrice>.*?<FormattedPrice>([^<]*)<\/FormattedPrice>/);
    $detail{"Author"} = $1 if ($buf =~ /<Author>([^<]*)<\/Author>/);
    # エラー?
    if ($buf =~ /<Errors>.*?<Message>([^<]*)<\/Message>/) {
        $detail{"ErrorMsg"} = $1;
    }

    return %detail;
}

# ---------------------------------------------------------------------
# デバッグ用
sub print_debug {
    return if (!$debug_mode);

    my $fd = new FileHandle;
    $fd->open("/path/to/log/output/directory/logfile.log", "a");
    print $fd "$_[0]";
    $fd->close();
}

sub escape {
  my $s = shift;

  $s =~ s/([^\0-\x7F])/do {
    my $o = ord($1);
    sprintf("%c%c", 0xc0 | ($o >> 6), 0x80 | ($o & 0x3f));
  }/ge;

  return uri_escape($s, $unsafe);
}

1;
ちなみに、オリジナルに追加した機能は「AWSWORD:perl」と書くとItemSearch結果の1番目を表示する...というモノグサ機能です。

Amazon Hacks 世界最大のショッピングサイト完全活用テクニック100選 Amazon Hacks 世界最大のショッピングサイト完全活用テクニック100選
ポール・ボシュ, 篠原 稔和, ウェブ・ユーザビリティ研究会
オライリー・ジャパン 単行本 / ¥3,670 (2004年04月24日)
 
発送可能時間:


モダンPerl入門 (CodeZine BOOKS) モダンPerl入門 (CodeZine BOOKS)
牧 大輔
翔泳社 大型本 / ¥58 (2009年02月10日)
 
発送可能時間:

Posted at by




久々tthttpd(tinytinyhttpd)を触っていて、動いてるだろうと思ってたkeep-aliveのコードが動いていなかった事に愕然としながら修正してたら、以前smegheadさんが報告してくれていたmingw32で落ちる問題の原因を発見。
記憶の底で、_beginthread()したらCloseHandle()しなきゃいねないと思い込んでいたんですが、どうやら_beginthread()じゃなくて_beginthreadex()の場合だけだった(参照)。_beginthread()の場合はスレッド終了時にリソース回収されるらしい。
他いろいろと修正してkeep-aliveが動くようになり、ベンチマーク取ってみた。
テストは「helloworld」と書かれたhello.txtをabでGETする物。

まずはconnection closeの場合
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /hello.txt
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   0.993 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      160000 bytes
HTML transferred:       11000 bytes
Requests per second:    1006.77 [#/sec] (mean)
Time per request:       9.933 [ms] (mean)
Time per request:       0.993 [ms] (mean, across all concurrent requests)
Transfer rate:          157.31 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     1   10  13.0      6     128
Waiting:        0   10  12.9      6     128
Total:          2   10  13.0      6     129

Percentage of the requests served within a certain time (ms)
  50%      6
  66%      7
  75%      7
  80%      7
  90%     15
  95%     31
  98%     59
  99%     86
 100%    129 (longest request)
そしてconnection keep-aliveの場合
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        
Server Hostname:        localhost
Server Port:            8080

Document Path:          /hello.txt
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   0.559 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    1000
Total transferred:      185472 bytes
HTML transferred:       11088 bytes
Requests per second:    1789.60 [#/sec] (mean)
Time per request:       5.588 [ms] (mean)
Time per request:       0.559 [ms] (mean, across all concurrent requests)
Transfer rate:          324.14 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     0    6  19.7      1     263
Waiting:        0    5  19.0      0     262
Total:          0    6  19.8      1     263

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      4
  75%      4
  80%      4
  90%     11
  95%     22
  98%     46
  99%     80
 100%    263 (longest request)
ちなみに同じマシン(Ubuntu9.10 on CeleronM 1.5GHz 500M)で取ったapache2のベンチ。
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        Apache/2.2.11
Server Hostname:        localhost
Server Port:            80

Document Path:          /~mattn/hello.txt
Document Length:        11 bytes

Concurrency Level:      10
Time taken for tests:   0.771 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Keep-Alive requests:    1000
Total transferred:      464920 bytes
HTML transferred:       11000 bytes
Requests per second:    1297.49 [#/sec] (mean)
Time per request:       7.707 [ms] (mean)
Time per request:       0.771 [ms] (mean, across all concurrent requests)
Transfer rate:          589.09 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.5      0       7
Processing:     6    8   3.3      7      28
Waiting:        6    7   3.1      6      27
Total:          6    8   3.3      7      28

Percentage of the requests served within a certain time (ms)
  50%      7
  66%      7
  75%      7
  80%      7
  90%     12
  95%     16
  98%     18
  99%     27
 100%     28 (longest request)
あまりパフォーマンスは意識してなかった割には、まぁまぁな速度(Request per secondでapache2の1.5倍速くらい?)が出たんじゃないかと思います。ただ機能も小さいですしapacheにも幾らかモジュールが入っちゃってますから参考値にしかすぎませんが...
とりあえずmingw32で不意に落ちる問題が解決したのでこれからパフォーマンスも気にしながらやって行こうかなーと思います。


Effective C++ 原著第3版 Effective C++ 原著第3版
スコット・メイヤーズ, 小林 健一郎
ピアソン・エデュケーション 大型本 / ¥5,987 (2006年04月29日)
 
発送可能時間:


Effective STL: STLを効果的に使いこなす50の鉄則 Effective STL: STLを効果的に使いこなす50の鉄則
スコット メイヤーズ, Meyers,Scott, 昭, 細谷
桐原書店 単行本 / ¥4,980 (2002年01月01日)
 
発送可能時間:

Posted at by