JavaはPerlよりも比較にならないほど速い?

川合孝典(2002/3/12)


はじまり

PHPとの比較のときから話が出てはいたんですが、 JavaとPerlとどっちが速いのかという気になるひとには気になる(気にならない人は気にならない(^^))話をちょっと調査してみようということで。

とはいえ、ちょっとは気になるというわけで、2つのケースについてテストしてみました。

1つは非常に単純なケースで、こちらのページでのベンチマークをベースに、単にページを表示するだけです。

  http://java-house.etl.go.jp/ml/archive/j-h-b/027274.html#body

これだけではやる前からPerlが圧倒的に勝つ(というよりTOMCAT対mod_perlなんですけどね)のは当然なので、以下のURLを参考に

http://java-house.etl.go.jp/ml/archive/j-h-b/030140.html  

Javaに有利な(^^)計算問題を解かせるようなプログラムの2パターンで試してみました。

あわせて「三大美徳」を支えるPerlの仕組 もご覧いただくと参考になるかもしれません。


結論

まずApacheモジュールであるmod_perlやmod_phpとTOMCATを比較するのは、それ自体がおかしな話といって過言ではないでしょう。やっぱりのmod_perlの方が速いのでした。もっともPerl側にCGI.pmを使って比較するのは適切ではないなぁというのは、思うのですが。

反対に計算問題になると当然のごとくJavaの勝ち。この場合にはPerlもコンパイルされた結果ですから、 Perlにおける変数扱いの自由度のせいでしょう。当然こうした計算が多くなるほどPerlには不利。(さらにPHPは...ってことでしょう)

言語としてはPerlのほうが"遅い"といえば、そうなんでしょうねぇ。ただもっと単純な計算や正規表現を使った文字列操作になると大分様子が違ってくるでしょう。さらに、どの時点からの計測かによって感じ方はかなり違いそうです。起動ロスはPHP<Perl<Javaでしょうから、単純なものをJavaで作るのは合わないかもしれません。

もちろんJavaの場合には、実行環境をどうするかによって大きく違ってくるでしょう。そういった意味からもJavaを使うんならウチはでかいからアプリケーション・サーバーを使うんだもんね ぐらいの心構えが必要なのか知らんと思うのです。管理なんかも含めて。

それでもスピードで負けるのは悔しいという方には、エジソンのお言葉を

私は数学が苦手だ。でも数学者を雇うことはできる

確かこうだったと思うんですが間違っているかもしれません。<(__)> 心意気としてはこういうことだということで、 Perlとしては速い言語があれば、それをPerlから利用するのが正しい選択でしょう。今回は、まずはInline CをそれからXSを利用したCのモジュールも作ってみました。そうなれば、そうそう負けることはないようです。

PerlからJavaあるいはその反対も可能なはずですしね。(JPLってその後どうなったんでしょうね?)

今はCORBAで連携させてみようかなぁとJDKの新しいバージョンをダウンロード中。連携させるまでに手間取りましたが(というか今でも無理やり通しているだけ)とりあえず完成でも呼出に時間が掛かる分、数字的にはあまりよくないです。 CORBA連携に関してはちょっとややこしい(Java側がねぇ)ので、別のページでやり方をまとめようと思います。

ご参考:洗練されたPerl: CおよびJavaプログラマーのためのPerl 5.6


環境

テスト方法

Apacheのベンチマーク abを利用し、以下のようなコマンドで

./ab -c10 -n100 http://lins/perl/tpg.pl?message=kaba

それぞれ5回実行、Requests per secondを記録。 (そのようなシェルスクリプトを作成したんですが)

結果

単純な起動のパターン

呼出の種類は以下の5つ。それぞれのソースは別途。 (1) Java : Java ServletをTOMCATを利用して呼出し (2) Perl- CGI.pm : mod_perlによる呼出。CGI.pmを利用 (3) Perl- Apache : mod_perlによる呼出。Apache APIを利用 (4) PHP : mod_phpによる呼出。 (5) HTML : スタティックなHTMLファイル

 

  Java Perl/CGI.pm Perl/Apache PHP HTML
1回目 52.58 70.03 164.47 201.21 361.01
2回目 46.40 98.23 198.41 202.84 364.96
3回目 47.57 90.42 206.61 211.42 350.88
4回目 49.33 100.91 205.34 203.67 363.64
5回目 49.12 71.74 182.48 195.31 355.87
平均回数 49.00 86.27 191.46 195.31 355.87
1回の時間(ms) 20.41 11.59 5.22 4.93 2.78
 

ちなみに普通のCGIによるPerlの場合には平均3.30回/303.4msってこれと比較しても、方式が違いすぎるので意味なさ過ぎ。さらにPerl側でCGI.pmの機能をなぜか多用しているスクリプトを他のものにあわせて文字列を編集するように変更するだけで、一回の呼出に2msほどの違い(平均回数でいうと20回ほどの違い)が出ました。比較するときにはなるべく同じ方式にして欲しいものですねぇと思ったりして。

計算問題入り

(1) Perl(Perlのみ) (2) Java (3) Perl (+Inline C) (4) Perl + XS モジュール (5) PHP (比較用)

 

パラメータが100のとき

  Perl Java Perl+Inline PHP
1回目 40.39 52.80 36.91 23.61
2回目 41.53 46.84 48.12 23.78
3回目 42.88 43.52 91.24 24.31
4回目 51.87 47.37 41.89 24.17
5回目 50.56 47.44 107.99 24.34
平均回数 45.45 47.59 65.23 24.04
1回の時間(ms) 22.00 21.01 15.33 41.59
 

パラメータが500のとき

  Perl Java Perl+Inline PHP
1回目 7.39 49.00 26.01 2.73
2回目 8.03 43.44 59.52 2.79
3回目 8.05 45.68 37.73 2.71
4回目 7.94 44.78 76.28 2.79
5回目 8.04 45.89 32.03 2.7
平均回数 7.89 45.76 46.31 2.75
1回の時間(ms) 126.74 21.85 21.59 363.37
 

ここからはPerl+C(XS)とJavaの直接対決(^^)だけにしました。

パラメータが5000のとき

  Perl+XS Java
1回目 7.66 8.17
2回目 8.51 7.85
3回目 8.46 7.81
4回目 8.42 7.80
5回目 8.41 7.80
平均回数 8.29 7.89
1回の時間(ms) 120.60 126.81
 

もしかしたら追いつかれるかなぁ?もっともそんなに計算が得意ならJavaにお任せするんだろうけど。 Inline Javaの場合はVMを起動してたんじゃ重すぎだろうし?別途コマンドラインでも調べてみましたが、そちらでは100,000までやったけど、 Java 9.7秒に対してPerl+XS9.3秒ぐらい。起動部分だけの比較でいうと0.61対0.08だから気持ちつめられたといえるのかどうか。こうなると誤差といってもいいようなレベルかなぁと思ったりするんですが。


ソース:単純編

Java

TomcatのサンプルについていたHelloWorldExampleをそのまま乗っ取って http://java-house.etl.go.jp/ml/archive/j-h-b/027274.html#body にあるソースを参考に作成しました。"メッセージ"とあったところは "Message"と変更しています。文字化けしないんやろか?
import java.io.PrintWriter;
 import java.io.IOException;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 public class HelloWorldExample extends HttpServlet {
     public void doGet(HttpServletRequest request,
                       HttpServletResponse response)
         throws ServletException, IOException {
         StringBuffer sb = new StringBuffer("<html><head>");
         sb.append("<title>Simple Echo Sample</title>");
         sb.append("</head><body bgcolor=\"#ffffff\">");
         sb.append("Message...<br>");
         sb.append(request.getParameter("message"));
         sb.append("</body></html>\n");
         response.setContentType("text/html; charset=iso-2022-jp");
         PrintWriter pw = response.getWriter();
         pw.print(sb.toString());
         pw.close();
     }
 }
 

Perl-CGI.pm

#!/usr/bin/perl
 use CGI qw(:standard :html3);
 $cgi = new CGI;
 print $cgi->header(
   -CONTENT_TYPE => "text/html; charset=iso-2022-jp"
                    );
 print $cgi->start_html(
   -TITLE => "Simple Echo Sample",
   -BGCOLOR => '#ffffff'
                        );
 print 'Message...<br>';
 print $cgi->param ('message');
 print $cgi->end_html;

Perl-Apache

#!/usr/bin/perl
 use Apache;
 my $r= Apache->request();
 $r->send_http_header('text/html; charset=iso-2022-jp');
 my %hArgs = $r->args();
 my $sMsg = "<html><head>" .
         "<title>Simple Echo Sample</title>".
         "</head><body bgcolor=\"#ffffff\">".
         "Message...<br>".
         $hArgs{message}.
         "</body></html>\n";
 print $sMsg;

PHP

<html><head><title>Simple Echo Sample</title></head>
 <body bgcolor="#ffffff">Message...<br><?php echo $message ?></body></html>

HTML

<html><head><title>Simple Echo Sample</title></head><body bgcolor="#ffffff">Message...<br>kabadesu</body></html>
 

ソース:計算問題

計算については http://java-house.etl.go.jp/ml/archive/j-h-b/030140.html のプログラムをベースにして作成しました。

Java

 import java.io.PrintWriter;
 import java.io.IOException;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 public class HelloWorldExample extends HttpServlet {
   private boolean isprime( int i ) {
     int j;
     for( j=2; j*j <= i ; ++j ) if( i%j == 0 ) return  false;
     return  true;                      /* prime */
   }
   private int prime(int limit) {
     int     c = 0;
     int     p;
     for( p=2; c<limit; ++p )
       if( isprime( p ) ) ++c;
     return p;
   }

     public void doGet(HttpServletRequest request,
                       HttpServletResponse response)
         throws ServletException, IOException {
         StringBuffer sb = new StringBuffer("<html><head>");
         sb.append("<title>Simple Echo Sample</title>");
         sb.append("</head><body bgcolor=\"#ffffff\">");
         sb.append("Message...<br>");
         sb.append(request.getParameter("message"));
         sb.append("CNT:" + prime(
             Integer.parseInt(request.getParameter("limit")) ) + "<BR>");
         sb.append("</body></html>\n");
         response.setContentType("text/html; charset=iso-2022-jp");

         PrintWriter pw = response.getWriter();
         pw.print(sb.toString());
         pw.close();
     }
 }

Perl(通常)

#!/usr/bin/perl
 use strict;
 use CGI;
 my $cgi = new CGI;
 sub isprime($) {
     my ($i)=@_;
     for(my $j=2; $j*$j <= $i ; ++$j ){
         return 0 if( $i%$j == 0 );
     }
     return  1;
 }
 sub prime($) {
     my ($limit)=@_;
     my $c = 0;
     my $p;
     for($p=2; $c<$limit; ++$p ){
       ++$c if( isprime( $p ) );
     }
     return $p;
 }
 print $cgi->header(
   -CONTENT_TYPE => "text/html; charset=iso-2022-jp"
                    );
 my $sMsg = "<html><head>" .
          "<title>Simple Echo Sample</title>".
          "</head><body bgcolor=\"#ffffff\">".
          "Message...<br>".
          $cgi->param('message') .
         'CNT:' . prime($cgi->param('limit')) . '<BR>'.
          "</body></html>\n";
 print $sMsg;

Perl(Inline)

#!/usr/bin/perl
 use strict;
 use CGI;
 use Inline C => <<CODEEND;
 long isprime( long i ) {
     long j;
     for( j=2; j*j <= i ; ++j ) if( i%j == 0 ) return  0;
       /* not prime */
     return  1;                      /* prime */
 }
 int prime(long lmt) {
     long     c = 0;
     long     p;
     for( p=2; c<lmt; ++p ) if( isprime( p ) )  ++c;
     return p;
 }
 CODEEND
 my $cgi = new CGI;
 print $cgi->header(
   -CONTENT_TYPE => "text/html; charset=iso-2022-jp",
                    );
 my $sMsg = "<html><head>" .
          "<title>Simple Echo Sample</title>".
          "</head><body bgcolor=\"#ffffff\">".
          "Message...<br>".
          $cgi->param('message') .
         'CNT:' . prime($cgi->param('limit')) . '<BR>'.
          "</body></html>\n";
 print $sMsg;

Perl(XS)

*Perl部分

#!/usr/bin/perl
 use strict;
 use CGI;
 use Prime_xs;
 my $cgi = new CGI;
 print $cgi->header(
   -CONTENT_TYPE => "text/html; charset=iso-2022-jp",
                    );
 my $sMsg = "<html><head>" .
          "<title>Simple Echo Sample</title>".
          "</head><body bgcolor=\"#ffffff\">".
          "Message...<br>".
          $cgi->param('message') .
         'CNT:' . Prime_xs::prime($cgi->param('limit')) . '<BR>'.
          "</body></html>\n";
 print $sMsg;

*Prime_xsモジュールの作り方

  long prime(long lmt);

Prime_xs.xsの変更

関数の定義を追加するのと、カレントディレクトリにあるprime_xs.hを見つけるように変更するという2点の変更が必要になります。

5c5
 < #include <prime_xs.h>
 ---
 > #include "prime_xs.h"
 27a28,33
 > long isprime(long i) {
 >     long j;
 >     for( j=2; j*j <= i ; ++j )
 >         if( i%j == 0 ) return  0; /* not prime */
 >     return  1;                    /* prime */
 > }
 31d36
 <
 36a42,52
 > long
 > prime(lmt)
 > long lmt
 > CODE:
 > {
 >    long c=0, p;
 >    for(p=2; c<lmt; ++p) { c+=isprime(p); }
 >    RETVAL = p;
 > }
 > OUTPUT:
 >   RETVAL

全体だとこんな感じ

 #include "EXTERN.h"
 #include "perl.h"
 #include "XSUB.h"

 #include "prime_xs.h"

 static int
 not_here(char *s)
 {
     croak("%s not implemented on this architecture", s);
     return -1;
 }

 static double
 constant(char *name, int arg)
 {
     errno = 0;
     switch (*name) {
     }
     errno = EINVAL;
     return 0;
 not_there:
     errno = ENOENT;
     return 0;
 }
 long isprime(long i) {
     long j;
     for( j=2; j*j <= i ; ++j )
         if( i%j == 0 ) return  0; /* not prime */
     return  1;                    /* prime */
 }
 MODULE = Prime_xs               PACKAGE = Prime_xs
 double
 constant(name,arg)
         char *          name
         int             arg
 long
 prime(lmt)
 long lmt
 CODE:
 {
    long c=0, p;
    for(p=2; c<lmt; ++p) { c+=isprime(p); }
    RETVAL = p;
 }
 OUTPUT:
   RETVAL

PHP

<html><head><title>Simple Echo Sample</title></head>
 <body bgcolor="#ffffff">Message...<br><?php
  function isprime($i) {
      for($j=2; $j*$j <= $i ; ++$j ){
          if( $i%$j == 0 ) {return 0; }
      }
      return  1;
  }
  function prime($limit) {
      $c = 0;
      for($p=2; $c<$limit; ++$p ){
        $c += isprime($p);
      }
      return $p;
  }
  echo $message . 'CNT:' . prime($limit) . '<BR>';
 ?></body></html>

 
河馬屋二千年堂