ぷぎがぽぎ このページをアンテナに追加 RSSフィード

2012-06-18

[][] OrePHPはこれでさらに高速になった(OrePhalcon)

前回、OrePHPはシンプルで速いクールなフレームワーク - ぷぎがぽぎという内容の記事を書きましたが、これぐらいの速度で満足してはいけないということで更に高速化を考えました。

Cのエクステンションで書かれた超高速PHPフレームワーク Phalcon

Phalconというフレームワークをご存知でしょうか?フレームワークをCのエクステンションで書いちゃった超高速PHPフレームワークです。

最速フレームワークと宣言してあるだけのことがあるベンチマーク結果が以下のグラフです。

f:id:brtRiver:20120619020407p:image

前回のベンチマークをも超越しそうな結果です。というわけで、早速HelloWorldでOrePHPと比べてみます。

まずはPhalconをインストール

エクステンションなのでgitからcloneしてきたものをいつもどおりphpnizeしてmake。

あとはphp.iniに

extension=phalcon.so

を追加。

環境はPHP5.4 + nginx なので

nginxの設定に以下を追加

        location / {
          if (!-e $request_filename) {
            rewrite ^/(.+)$ /index.php?_url=$1 last;
            break;
          }
        }

_urlというパラメータで渡すのを忘れずに。

準備はできたのでさっそくベンチ。

nametrans/secrelative(%)
OrePHP484.76100%
Phalcon738.94152%

はい。簡単に負けましたw

最速と自負するだけのことはあります。

じゃあ、Phalconをベースにすればいいじゃまいか

OrePHPではSymfony ComponentsのRoutingコンポーネントを使っていましたが、たぶんこれがCで書かれていないから負けるんだということで、Phalconのルーティングを使うようにしてOrePhalconを作ってみました。

<?php
ini_set('display_errors', 1);
error_reporting(-1);

$router = new Phalcon_Router_Regex();
$router->setBaseUri('/phalcon');
$router->add("hello/(\w+)", array(
    "controller" => "hello",
    "action" => "say",
    "name" => 1,
));

$router->handle();
$controller = $router->getControllerName();
$action =  $router->getActionName();
$params = $router->getParams();

try{
  $controllerFilePath = __DIR__ . '/../app/controllers/' . $controller . ".php";
  if (!file_exists($controllerFilePath)) {
    throw new Exception("controller file is not found");
  }
  require $controllerFilePath;
} catch (Exception $e) {
  echo $e->getMessage();
  exit;
}

$class = ucfirst($controller);
echo $class::$action($params);

今回は最速を目指すのでフレームワークとしてコアファイルはこのファイル1つです。

フレームワークのコアクラスはエクステンションで書かれているのでrequireすら不要です。

あとは、実際に呼び出されるコントローラーファイルをHello.phpとして用意します。

<?php
class Hello
{
  public static function say($params)
  {
    return "Hello " . $params['name'];
  }
}

OrePhalconは最速になれたのか?

nametrans/secrelative(%)
OrePHP484.76100%
Phalcon738.94152%
OrePhalcon923.60191%

はい。最速でした。

Phalconほんと速いですね。

2012-06-09

[][]OrePHPはシンプルで速いクールなフレームワーク

[追記1] 2012-06-10: ベンチマークを追加

[追記2] ブクマのコメントに回答

[追記3] ベンチマークをちょっと充実させた。Pinocoはえー

[追記4] コントローラーの仕組みを変更 & debugモード追加

[追記5] PHP5.4.4で再ベンチ

"ぼくがかんがえたさいきょうのふれーむわーく"ではないですが、OrePHPというPHP Webアプリケーション フレームワークを1つ書いてみた。

no title

こんせぷと

フレームワークが提供するのはルーティングだけ。シンプルに。速く。

ぼくがほしいのは、るーてぃんぐ

素のPHPでWebアプリケーションを書きたくない理由の1つがルーティングを用意するのが面倒というのがあります。Symfonyだとyaml、php、xmlなどで定義できて結構便利です。つまり、自分が欲しいのはルーティングだけなんだというのは結構多いような気がします。というわけで、Symfony Component Routingを利用できるようにしただけのフレームワークです。なので、コアコードは70行ちょっと。

るーてぃんぐ・ふれーむわーく

仕組みは簡単。ルーティングの定義をconfig/routing.yaml に書きます。

書き方はSymfonyとほぼ同じ。どのコントローラーを呼ぶかを定義すればOK。

hello:
    pattern: /hello/{name}
    defaults: { controller: 'hello' }

これだと/hello/hogeでアクセスされると app/controller/hello.phpがincludeされるというだけ。

あとは、hello.phpでやりたいことを書いて、returnでブラウザに返したい文字列を書くだけ。

ここで、hello.phpで無名関数をreturnするようにします。そして無名関数内のreturnでブラウザに返したい文字列を書くだけ。

<?php
return function($request, $params, $container) {
    $now = date('Y-m-d H:i:s');
    return "Hello world " . $params['name'];
};

見ての通りクラスじゃないです。普通のPHPファイルです。よくMVCフレームワークだとアクションという概念がありますが、これには無いです。コントローラーだけです。

無名関数に渡される引数は、リクエストオブジェクト(Symfony2と同じ)、ルーティングで取得したパラメータDIコンテナ(Pimple)です。

ただ、returnで文字列返すのは面倒だ。やっぱりTwigぐらい使いたいってのはありますよね。その場合は次のように書けばOK

<?php
return function($request, $params, $container) {
    $now = date('Y-m-d H:i:s');
    return $container['tpl']->render('hello.html', array('now' => $now, 'name' => $params['name']));
};

$container['tpl']というのがTwigのインスタンスになってるので、この場合はだとapp/views/hello.htmlがtwigのテンプレートとして処理されます。簡単ですね。また、Twigのインスタンスは遅延読込になっているので呼ばない限り読込すらしません。このあたりはPimpleというDIコンテナを使ってよしなにやってます。つまり、Pimpleのインスタンスが$containerですね。

ふつうにはやい

一番重い処理はrouting.yamlの解析だと思いますが、一度解析するとcache/ProjectUrlMatcher.php にキャッシュし、2度目以降は解析しません。普通にPHPのクラスファイルを読み込むだけになるので速いです。ということで、routing.yamlを書き換えたらキャッシュされているファイルを手動で削除しなくちゃいけません。

そうしないと、変更後の内容が反映されないので。

また、デバッグモードを追加しました。

index.phpで

$app->c['debug'] = true;

と追記してデバッグモードを有効にすれば毎アクセスごとにrouting.yamlをパースします。開発時には有効にしておくと便利です。

(追記)簡単なHello World的なベンチマーク結果は以下のとおり。PHP5.4.3(php-fpm)+apc+nginxのvmにsiegeで計測。あくまでオレ環境なので参考程度で。

(追記2)FuelPHPを再計測

(追記3)PHP5.4.4で再ベンチ

siege -b -c 10 -t 3S http://xxxx/xxx で検証
nametemplate enginetrans/secresult(relative)
FuelPHP 1.2Twig130.0463%
FuelPHP 1.2-169.0882%
SilexTwig170.6383%
Silex-205.61100%
OrePHPTwig342.89167%
Pinoco(dev-master)PHPTAL352.11171%
CodeIgniter 2.1-373.02181%
Pinoco(dev-master)-429.84209%
OrePHP-484.76236%

SilexのTwig無バージョンを基準としてみると、2倍ぐらいのレスポンスは叩きだせますね。わーい。

でーたべーすとかは?

なにそれ?おいしいの?

せっていふぁいるとかは?

routing.yamlだけあります。ほかは$app->cがDIコンテナなのでよしなにやってね。web/index.phpでTwigをほり込んでるあたりが分かりやすいかと。ちなみにindex.php全体でもたったこれだけ。

<?php
require __DIR__ . '/../vendor/autoload.php';
$app = new Ore\Framework;
$app->c['tpl'] = $app->c->share(function($c){
    $loader = new Twig_Loader_Filesystem($c['base_dir'] . '/app/views');
    return new Twig_Environment($loader,array(
                                  'cache' => $c['cache_dir'],
                                  ));
  });
// $app->c['debug'] = true;
$app->display();

つかいかた

githubからgit cloneなりダウンロードしてきたら

$ php composer.phar install

で外部ライブラリインストールするだけ。

cacheディレクトリは実行者が書き込めるように0777にしておきましょう。

あとは、ブラウザからhttp://example.com/hello/orephpにアクセスすればOK

コメントありがとうございます

以下のコメントをいただきました。ありがとうございますm(__)m

なぜPHPを直書きさせてないのか?について
  • yaml じゃなくて素の PHP の方が速くなりそうなんだけど、どうなんだろう。 by id:heavenshellさん
  • キャッシュを手動削除する手間が必要なら、素直にPHP直書きさせたらいいのに by id:terurouさん

キャッシュを利用しているのは速くなるからなのですが、yamlよりPHPの方がと考えが浮かぶのはわかります。しかし、実際にどのようにキャッシュされているかを見ると納得してもらえるのではないかと...。

routing.ymlで以下のように定義したファイルは

hello:
    pattern: /hello/{name}
    defaults: { controller: 'hello' }
default:
    pattern: /{controller}
    defaults: { controller: controller }

以下のような cache/ProjectUrlMatcher.php に変換されキャッシュされます。

<?php

use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RequestContext;

/**
 * ProjectUrlMatcher
 *
 * This class has been auto-generated
 * by the Symfony Routing Component.
 */
class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher
{
    /**
     * Constructor.
     */
    public function __construct(RequestContext $context)
    {  
        $this->context = $context;
    }

    public function match($pathinfo)
    {  
        $allow = array();
        $pathinfo = rawurldecode($pathinfo);

        // hello
        if (0 === strpos($pathinfo, '/hello') && preg_match('#^/hello/(?<name>[^/]+)$#s', $pathinfo, $matches)) {
            return array_merge($this->mergeDefaults($matches, array (  'controller' => 'hello',)), array('_route' => 'hello'));
        }

        // default
        if (preg_match('#^/(?<controller>[^/]+)?$#s', $pathinfo, $matches)) {
            return array_merge($this->mergeDefaults($matches, array (  'controller' => 'controller',)), array('_route' => 'default'));
        }

        throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException();
    }
}

なんとなくyamlファイルがただパースされてPHPの配列になると想像する方もいるかもしれませんが、Symfony Component Routingはこのように、DSLで複雑なPHPコードを抽象化し記述できるようにしてくれています。なので、今回はyamlを選択していますが、xmlでもphpの配列でも定義することができ、最終的には同じPHPコードに変換されます。さすがにこのクラスコードをルーティングを追加、編集するたびに書けというのは辛いですよね。

参考: ドメイン特化言語とモデル駆動エンジニアリング - Johan den Haan - Digital Romanticism

とはいえ、開発用にキャッシュしないオプションがあったほうがいいですよね。

index.phpでデバッグモードを有効にしている場合はキャッシュを利用せずに毎回パースするようにしました。

$cとか突然でてくるのキモいよね
  • $cとか突如出現するの怖いし、$thisにできないですかね。(再代入防げるし)by id:escape_artistさん

はい。キモイですよね。うん。キモイとおもいます。

まだまだ改良の余地はありますよね。せめてクラスメソッドで閉じ込めるようにしたいと思います。

改良し、無名関数に閉じ込めました。

オレオレフレームワーク作るの楽しいですね。

2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |