PHPとRubyとPythonのパフォーマンスを比較してみました

前回のエントリが結局フレームワークの話になってしまったため、新しいエントリを書きました。

同じような処理をさせた場合、どの言語が一番早いのか。非常に気になるところでありますが、試したことがありませんでした。今回試してみたところ、意外な結果が出たのです。

下の方に書いたそれぞれのプログラムを実行した結果の、プログラム内部で計測したミリ秒を比較してます。

PHP Ruby Python
2.80秒 0.36秒 0.93秒

3回計測した平均です。同じ処理をさせるプログラムを書いたつもりですが、本当にフェアなものになっているのか正直自信がありません。そもそもPHPはWebアプリを主な目的としているとのことなので、こういう処理は得意としていないのかもしれない。

また、それぞれのプログラムのループ内2行目、文字列連結の部分をコメントアウトして実行してみると、これまた意外な結果が出ました。

PHP Ruby Python
0.07秒 0.06秒 0.39秒

PHPは文字列連結が苦手なんだろうか。暗黙的な型変換にコストがかかるのだろうか、等々考えてしまいます。それとも秒を求める部分がネックなんですかね。その辺は今後追っていきます。

ただ、これまでPythonの方がRubyより早い、と思っていたために自分の無知さに猛烈に恥ずかしい思いをしています。

実際にMySQLと連携するバッチを書いたときにはRubyの方が遅かったり、Railsアプリがもっさりだったり、「速い」という印象がまったくありませんでした。が、それはある特定の面しかみていない上での思い込みだったのかもしれません。

これはRailsが遅いのか、もしくはMySQL/Rubyが遅いのか。当時の書き方がマズかったのか。今後、いろんなケースでこういった比較をしてみようかと。

今回の検証がすべてではないし、これが答えだとも思っていません。また、そういう意図を含んだエントリではありません。今回の検証だって、特定の面しか見ていません。が、あまりに意外な結果だったため、自戒の念を含めてブログに書きました。

「こう書くともっとフェアだよ」「こういうテストの方がいいんじゃない」等、アドバイスいただけるとうれしいです。

フレームワークまで含めた検証した方が現実的なのだろうか。。。


PHP

#!/usr/bin/php
<?
function microtime_float()
{

list($usec, $sec) = explode(” “, microtime());
return ((float)$usec + (float)$sec);

}
$_start = microtime_float();
$_sec = date(‘s’);

for($i=1;$i<100000;$i++)
{

$_val = (mt_rand(0, 100) + $i) / $i;
$_str = $_val.date(‘s’);

}

echo microtime_float() – $_start;
?>

Ruby

#! /usr/local/bin/ruby

i=1
_start = Time.now

99999.times do

_val = rand(100) / i
_str = _val.to_s + Time.now.sec.to_s
i += 1

end

p (Time.now – _start).to_f

Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
import datetime
import random

_start = datetime.datetime.now()

for i in range(1, 100000):

_val = random.randint(0, 100) / i
_str = str(_val) + str(datetime.datetime.now().second)

print datetime.datetime.now() – _start


検証環境

CPU:Xeon 1.86GHz
メモリ:2GB
OS: CentOS5.4 32bit
PHP:5.2.11
Ruby:1.8.7
Python:2.5.2

「PHPとRubyとPythonのパフォーマンスを比較してみました」への11件のフィードバック

  1. それは本当に同じ処理でしょうか?
    乱数の精度や分散が異なっていて、良い乱数を生成しているから遅いということはないでしょうか?

    あと、Pythonは、先に
    randint = random.randint
    now = datetime.datetime.now
    しておいて、ループの中で
    randint()
    now()
    だけをするようにすると速くなります。 

    返信
    • コメントありがとうございます。
      確かに乱数の精度はあまり意識してませんでした。
      見直してみますね。ありがとうございます。

      返信
  2. PHPは date(‘s) の解釈に時間を持っていかれているのかなと。

    $_str = $_val.date(‘s’);

    $_str = $_val.time();

    これで4倍ぐらい早くなりませんか?
    もちろん time() と date(‘s’) では結果が異なってしまうんですけど、、、

    返信
  3. 乱数の精度で見ると、RubyもPythonもPHPも(PHPについてはこのコードの場合)も結局はメルセンヌ・ツイスタですから変わらないでしょう。

    また、場当たり的な速度改善をすればそれぞれ速くなるのは当然ですから、各言語に同程度習熟した人間が直感的に書いたコードの速度を比較した方がよいと思います。

    例えばRubyでも、
    _str = _val.to_s + Time.now.sec.to_s
    ではわざわざ3つも文字列オブジェクトを生成してしまい無駄が出るので、
    _str = _val.to_s << Time.now.sec.to_s
    にするとか、
    Pythonを for でまわすのにRubyは times と i += 1 では不公平ですから、
    1.upto(100000) do |i|
    とするなどすれば、10%ほど速度の改善が見込めますが、
    このような「これはこうすれば速くなる」をあまり持ち出してしまうと、
    それぞれの言語とその実装を熟知した人間同士の比較でなければいけなくなってしまい、
    大半の利用者(実際にコードを書く人の多く)の実態にそぐいません。

    しかしまあ、ハセテツさんが「今回の検証が答えではない」とおっしゃるとおり、「今回の処理についてはたまたま」、そして「今回選択された実装系ではたまたま」Rubyが一番得意だった、と見るのが妥当でしょうね。

    ところで、処理時間を計測するとき、この方法ではムラが出てしまいますから、こういう繰り返し処理全体を10000回か100000回ぐらい繰り返させて計測した方がよいと思いますよ。

    返信
  4. あ、3回の平均と書いてありましたね。
    この処理を100000回もやっていたら日が暮れますから、十分ですね。
    よく文章を読んでいませんでした。すみません。

    返信
  5. コメントありがとうございます。

    やはり言語としての差よりも、習熟度によるコーディング手法の差の方が大きく出ますね。

    返信
  6. このpythonプログラムは遅いと思う。

    range => xrange
    datetime => time とキャッシュ

    とか、他にも改善できるテクニックがいくつかあります
    でも知らないと難しいですよね。

    返信
  7. ピンバック: Way to Apple