こんな機能があるなんて知りませんでした。割と古いPHPで頭が止まっていましたが、久しぶりにPHPのドキュメントを呼んでいて、autoloadという機能があることを知りました。
大規模プロジェクトだと、クラスをincludeするだけで一苦労
超大雑把に説明すると、以下のようなソースコードはエラーを吐きます。
<?php $obj = new MyClass(); |
このとおり。
PHP Fatal error: Class 'MyClass' not found in /Users/katty/Documents/AutoLoader/index.php on line 2
そもそも、MyClassというクラスが定義されているファイルをincludeしないと使えないというエラーです。
規模が大きくなってきて、クラスが100個とか200個とかになってくると、includeするだけでも一苦労だったりしますね。
クラスを使うためにソースコードを自動でincludeしてくれる
そこで、「クラスのオートローディング」です。
spl_autoload_register() なり、__autoload() を使うことで、クラスが定義されていなかった場合に、必要なソースコードを動的にincludeすることができます。
でも遅いんでしょう?
というわけで、少しベンチマークしてみました。PHPのバージョンは5.3.10。マシンは、MacBook Air(1.7 GHz Intel Core i5)です。
bench.php
メインのファイル”bench.php”は以下のような形。時間を測定して、$argv[1]に指定した方式でクラスをincludeし、$argv[2]で指定した数だけクラスを呼び出しています。呼び出し方は2種類で、newでインスタンスを生成するパターンと、staticなメソッドを呼ぶパターンです。
<?php $begin = microtime(); include ('./' . $argv[1] . '.php'); for ($i = 0; $i < $argv[2]; $i++) { $class_name = 'Class' . $i; if ($i % 2 == 0) $class = new $class_name(); else $class_name::static_function(); } echo((elapsed_time($begin, microtime()) * 1000) . "ms"); function elapsed_time($begin, $end) { list($usec, $sec) = explode(" ", $begin); $begin = ((float)$sec + (float)$usec); list($usec, $sec) = explode(" ", $end); $end = ((float)$sec + (float)$usec); return $end - $begin; } |
require.php
requireでクラスを読み込む場合の測定のための”require.php”は以下です。事前に1000個のクラスをclassesディレクトリに作成しておいて、それを全部読み込んでいます。
<?php for ($i = 0; $i < 1000; $i++) require './classes/Class' . $i . '.php'; |
autoload.php
autoloadを使ってクラスを読み込む場合の測定のための”autoload.php”は以下です。クラスがなければ、ここで定義する関数に処理が渡り、クラス名を元にincludeを実行するものとしています。(今考えたら、ここもrequireにすべきでした・・・)
<?php spl_autoload_register(function($class) { include 'classes/' . $class . '.php'; }); |
requireを使う場合
まず、1000クラスを読み込んだ上で、10クラスを使う場合です。
$ php bench.php require 10 Class0::__construct Class1::static_function # ... 略 ... Class8::__construct Class9::static_function 56.918859481812ms |
続いて100クラス使う場合。
$ php bench.php require 100 Class0::__construct Class1::static_function # ... 略 ... Class98::__construct Class99::static_function 55.775880813599ms |
1000クラス使う場合。
$ php bench.php require 1000 Class0::__construct Class1::static_function # ... 略 ... Class998::__construct Class999::static_function 65.891027450562ms |
最初に1000クラスを読み込むので、使わないクラスは無駄に読み込まれたことになります。1000クラス読み込むとだいたい50msくらいで、それを使う時間は読み込む時間に比べると大きくないですね。
requireで1クラス読むのにかかる時間はだいたい0.5msというところでしょうか。
autoloadを使う場合
autoloadは必要になった場合にはじめて読み込みます。10クラス使う場合は、10個のソースコードを読み込ます。
$ php bench.php autoload 10 Class0::__construct Class1::static_function # ... 略 ... Class8::__construct Class9::static_function 1.3880729675293ms |
100クラスの場合。
$ php bench.php autoload 100 Class0::__construct Class1::static_function # ... 略 ... Class98::__construct Class99::static_function 11.373043060303ms |
1000クラスの場合
$ php bench.php autoload 1000 Class0::__construct Class1::static_function # ... 略 ... Class998::__construct Class999::static_function 100.74710845947ms |
不要なクラスを読まないので、10クラスの場合は、990クラスを無駄にしているrequireよりも速いです。1000クラスの場合は、どちらも無駄がないので、autoloadのオーバーヘッドが見える分、autoloadの場合の方が遅いです。
autoloadの場合はだいたい1クラスで1msというところです。
まとめ
autoloadで読み込むと、requireで読み込む場合の2倍くらい遅いです。しかし、クラスの読み込みを意識せずに実装することができるので、うまく使えば素晴らしい威力を発揮してくれそうです。でもパフォーマンスがシビアな場合は無理ですね。