require_once vs. no require_once - please read, critical information
| From: | Gregory Beaver | Date: | Sun, 23 Sep 2007 22:22:05 +0000 |
| Subject: | require_once vs. no require_once - please read, critical information | ||
| Groups: | php.pear.dev | ||
| Request: | Send a blank email to pear-dev+get-48138@lists.php.net to get a copy of this message | ||
Hi,
I thought it might be useful to have a definitive benchmark on
require_once, both of its performance AND maintainability, something
often neglected when benchmarking. Some definitions:
1) performance: the speed at which a PHP request can be served, the
number of requests per second as measured by apache benchmark
2) maintainability: the ease with which developers can develop, track
down missing dependencies, and re-configure older code to solve new
problems.
This email is organized into three sections (performance, maintenance
and conclusions), you can scroll down to read on maintenance or
conclusions if performance doesn't interest you, although there are some
surprising results in performance. I did not benchmark cramming
everything into a single file, an earlier benchmark verified by David
Coallier and Stanislav Malyshevsky at Zend confirms a 10-30% performance
difference even in the best case of using APC with apc.stat=0. [Note:
we're talking about removing require_once from *libraries*. The
top-level user application still would need to include the files, but if
require_once is removed from libraries, then the user can use "include"
without the "_once"]
===============
= performance =
===============
To address the question of #1, I took one of the most popular PEAR
packages, HTML_QuickForm, and used its elements.php from the docs folder
as a test bed. I chose this package and example for several reasons:
1) the example demonstrates a common usage of HTML_QuickForm: creating
complex HTML forms
2) HTML_QuickForm has many individual files and is quite complex
3) the example uses almost all of the files
4) there is a dependency on 2 external packages, PEAR and HTML_Common
A quick check revealed that 27 files were included to execute the
example, and nearly 50 require_once calls were executed at various
times, so the example has a fair amount of redundant require_once calls.
Here is the source of elements.php: http://pear.php.net/~greg/elements.phps
I then used a grep for ^\s*(include|require) to locate the 88 calls to
include_once/require_once, and commented them out in a fresh checkout of
HTML_QuickForm, with the exception of the includes in the Smarty
renderer, which won't play nice with autoload due to the use of SMARTY_DIR.
The modified package is available for installation from
http://pear.php.net/~greg/HTML_QuickForm_TESTING-3.2.9TESTING1.tgz
I renamed HTML_QuickForm to HTML\ so that paths would work as if
installed by PEAR (HTML_QuickForm\QuickForm.php became
HTML\QuickForm.php) so that I could have dual PEAR repositories, the
official one and the fake one with require_once removed. Then I created
an elements.php that relies on an __autoload() similar to
PEAR2_Autoload, and saved it as elements2.php.
Here is the source of elements2.php:
http://pear.php.net/~greg/elements2.phps
Last, I created an allfiles.php-like elements.php called elements4.php
(elements3.php is explained in maintainability below).
Here is the source of elements4.php:
http://pear.php.net/~greg/elements4.phps
I then created three files in one of my aliases for localhost, test.php,
test2.php and test3.php. Here is their source:
test.php:
<?php
require '/home/cellog/workspace/pear/HTML/QuickForm/docs/elements.php';
test2.php:
<?php
require '/home/cellog/workspace/pear/HTML/QuickForm/docs/elements2.php';
test3.php:
<?php
require '/home/cellog/workspace/pear/HTML/QuickForm/docs/elements4.php';
and I ran each test for 60 seconds through apache benchmark 3 times,
first without APC, next with APC, and last with APC and apc.stat=0.
Full text of benchmark is here: http://pear.php.net/~greg/output_perf.txt
Summary of results in requests/second:
no APC:
=======
require_once: 17.47 [#/sec]
autoload: 17.66 [#/sec]
allfiles: 17.79 [#/sec]
APC:
====
require_once: 31.25 [#/sec]
autoload: 32.32 [#/sec]
allfiles: 33.73 [#/sec]
APC apc.stat=0
==============
require_once: 21.91 [#/sec]
autoload: 18.59 [#/sec]
allfiles: 34.06 [#/sec]
The most surprising result is the last tests, with apc.stat=0. The
obvious trend of slower => faster as require_once => autoload =>
allfiles is broken when apc.stat=0, and instead we have allfiles as a
clear winner providing the best possible performance, but autoload
suddenly performing nearly at the level of APC being absent, and
require_once also performing extremely poorly. I would tend to argue
that this is a flaw in APC were it not true that with other applications
I have tested that use require_once, performance tends to improve
dramatically with apc.stat=0. I hesitate to project a general
conclusion on HTML_QuickForm's performance without further benchmarking
of other scripts, but the result is very surprising.
There are four conclusions that can be drawn from this benchmark:
1) full path allfiles-style include is always faster, require_once is
always slower, autoload is slightly faster than require_once
2) the performance difference between allfiles and require_once is
*insignificant* for users without APC, but the best performance can only
be achieved with allfiles.
3) APC makes a difference in performance :)
4) autoload is slightly, but not significantly faster than require_once
for this benchmark
The last conclusion is surprising to me, as autoload is always done at
run-time, I would expect it to be slower even without APC.
===================
= Maintainability =
===================
Now, on to maintainability. For the following tests, please ignore the
HTML tags, I ran them in Zend Development Environment, which uses the
CGI version of PHP. In my investigations, all PHP IDEs can process
class->file mappings without using require_once to do this. The reason
is that the majority of PHP developers don't code using include_path,
and so there is not much point in parsing require_once statements since
they rarely help to find the file containing a class.
The output of the error messages is available here:
http://pear.php.net/~greg/output.txt
Setting up elements2.php, I tried running the file without the include
of PEAR.php, or HTML_Common being installed. Here is the error message
I received:
=================================================
== Error message, missing dependency, autoload ==
=================================================
exception 'Exception' with message 'Class HTML_Common could not be
loaded from HTML/Common.php, file does not exist
(include_path="/home/cellog/workspace/pear") [PEAR2_Autoload version
1.0]' in /home/cellog/workspace/pear/HTML/docs/elements2.php:28
Stack trace:
#0 /home/cellog/workspace/pear/HTML/QuickForm.php(124):
__autoload('HTML_Common')
#1 /home/cellog/workspace/pear/HTML/docs/elements2.php(20):
include('/home/cellog/wo...')
#2 /home/cellog/workspace/pear/HTML/docs/elements2.php(33):
__autoload('HTML_QuickForm')
#3 /var/www/dummy.php(1): include('/home/cellog/wo...')
#4 {main}
Then, I forced an uninstallation of HTML_Common, and received this error
message running elements.php:
=====================================================
== Error message, missing dependency, require_once ==
=====================================================
<br />
<b>Fatal error</b>: require_once() [<a
href='function.require'>function.require</a>]: Failed opening required
'HTML/Common.php' (include_path='/usr/local/lib/php') in
<b>/usr/local/lib/php/HTML/QuickForm.php</b> on line <b>33</b><br />
Next, I tried running elements2.php without an __autoload() function and
received this error:
======================================================================
== Error message, missing dependency, allfiles (no autoload backup) ==
======================================================================
<br />
<b>Fatal error</b>: Class 'HTML_QuickForm' not found in
<b>/home/cellog/workspace/pear/HTML/docs/elements2.php</b> on line
<b>33</b><br />
Last, I added the autoload backup as seen in elements4.php:
<?php
function __autoload($class)
{
die ((string) new Exception('Class ' . $class . ' was not loaded
properly'));
}
?>
and received this error:
===================================================================
== Error message, missing dependency, allfiles (autoload backup) ==
===================================================================
exception 'Exception' with message 'Class HTML_Common was not loaded
properly' in /home/cellog/workspace/pear/HTML/docs/elements4.php:17
Stack trace:
#0 /home/cellog/workspace/pear/HTML/QuickForm.php(124):
__autoload('HTML_Common')
#1 /home/cellog/workspace/pear/HTML/docs/elements4.php(21):
include('/home/cellog/wo...')
#2 /var/www/dummy.php(1): include('/home/cellog/wo...')
#3 {main}
=============================
= debugging with __autoload =
=============================
Now, the steps that I had to take in order to make elements2.php
function were as follows:
1) download HTML_Common and put Common.php into the HTML/ directory
2) directly include PEAR.php from my global PEAR install
3) Change the case of the "required" rule to match the file name:
$GLOBALS['_HTML_QuickForm_registered_rules'] = array(
'required' => array('HTML_QuickForm_Rule_Required',
'HTML/QuickForm/Rule/Required.php'
It was:
$GLOBALS['_HTML_QuickForm_registered_rules'] = array(
'required' => array('html_quickform_rule_required',
'HTML/QuickForm/Rule/Required.php'
The flow of these steps would be (if I were a new user):
1) run elements2.php
2) get exception saying I need HTML_Common and the file HTML/Common.php
does not exist in the include_path, with a stack trace.
3) I look at each file of the stack trace, and see that HTML_QuickForm
extends HTML_Common on line 124
4) I google HTML_Common, and find the HTML_Common package [the
controversial dependency documentation would require that the
HTML_Common package be documented in HTML/QuickForm.php as an external
dependency, eliminating this step]
5) download it, extract HTML/Common.php into the right location
6) run elements2.php
7) see I need PEAR_Error
8) google PEAR_Error, find the PEAR package. Download it, get PEAR.php
and add include to elements2.php [this is also an inconsistency that
will not happen with PEAR2 - PEAR_Error is in PEAR.php]
9) run elements2.php
10) see I need html_quickform_rule_required, and notice that the case is
all lower-case. html/quickform/rule/required.php doesn't exist, but
HTML/QuickForm/Rule/Required.php does.
11) using the backtrace, figure out that I need to change the
_HTML_QuickForm_Registered_Rules array to have the correct case
12) it works.
The flow of these steps if I have Pyrus-installed HTML_QuickForm would be:
1) install HTML_QuickForm
2) run elements2.php
3) it works
Now, if we assume that the user has not set up __autoload(), the steps are:
1) run elements2.php
2) get fatal error
3) see we need HTML_Common class
4) google HTML_Common, get the package
5) download it and include it directly
6) repeat 27 times and give up
This is a likely scenario if autoload is poorly documented, or the
examples don't use PEAR2_Autoload.
===============================
= debugging with require_once =
===============================
Next, I uninstalled HTML_Common, and ran elements.php. The error I
received told me that HTML/Common.php could not be found, on which line
it was required, and the include_path. I did not know why
HTML/Common.php was required, as the line requiring it was not linked to
the location where it was used.
However, I knew I needed HTML_Common. HTML/QuickForm.php did not
mention the location or where to get HTML/Common.php, so my next step
would be to google HTML/Common.php, which turns up nothing
(http://www.google.com/search?q=HTML%2FCommon.php). Now I either know
to email pear-general, or I am stuck.
Here is the flow:
1) run elements.php
2) get fatal error
3) see that I need HTML/Common.php for some reason
4) google HTML/Common.php
5) get nothing, now what?
A more advanced user would know that HTML/Common.php is likely class
HTML_Common, and the flow would be:
1) run elements.php
2) get fatal error
3) google HTML_Common result #1 is HTML_Common package
4) download HTML_Common
The flow of these steps assumes (perhaps unfairly) that the user has
already installed PEAR, as most packages simply don't work without the
PEAR Installer
Comparing the two ways, I can draw three conclusions:
1) the number of steps required to locate HTML_Common is the same number
(5) between require_once/autoload
2) in require_once, a user without knowledge that HTML/Common.php =
HTML_Common class is likely to be stuck
3) with autoload, a user who does not use autoload is likely to find the
missing dependency, but unlikely to understand how to enable it, and
even less likely to be able to understand anything about programming.
===============
= Conclusions =
===============
* The performance difference between files with require_once and files
without require_once is relatively insignificant, but can be huge in
favor of no require_once under certain circumstances.
* The maintainability difference between files with require_once and
files without require_once is also relatively insignificant, with a
slight advantage in debugging information for developing with
__autoload() due to the stack trace and google-ability of class names
vs. files.
In short: the difference between performance and maintainability of the
two methods is small.
What *is* huge is the difference in flexibility. Code designed with
require_once in each file is always going to run the same way.
Re-configuring the source code, bundling into a single file, running
off-disk without setting up include_path, making into a phar - none of
these is possible without code change when using require_once. In
addition, if a difference of a few milliseconds is necessary,
require_once cannot provide this, but moving from __autoload() to
allfiles can.
Moving from __autoload() to an allfiles approach is simple. To prove
this, I created elements3.php for this purpose:
Source of elements3.php: http://pear.php.net/~greg/elements3.phps
When run, the file spits out the PHP include code with full paths that I
pasted at the top of elements4.php. The steps were simple, and
incredibly easy. Also possible is a hybrid of allfiles and
__autoload(), where __autoload() is only used for extreme edge cases
such as rarely called exceptions or other files not often called into
service.
As another example of flexibility, if I were to re-add require_once
statements to Pyrus, and wish to make it possible to install Pyrus to
disk without internet access (something I do wish to enable), I must
also bundle a .tgz of Pyrus, doubling the size of the .phar archive, and
increasing the complexity quite a bit of installing from an internal
file within the phar. If there are no require_once calls, I can
literally install Pyrus via:
(in bootstrap):
define('PYRUS_PHAR', __FILE__);
(in installation code):
$package = new PEAR2::Pyrus::Package(PYRUS_PHAR)
$install = new PEAR2::Pyrus::Installer;
$install->install($package);
with a half-size phar, and no code changes, this makes the code
tremendously more maintainable (less variations of the same code always
means it is more maintainable).
For a web application, with require_once in each file, the ability to
bundle PEAR2 dependencies inside will require that the code have its own
include_path, meaning relative includes may conflict with files inside
the application. The solution for this issue that most applications
follow is to use a constant and include files relative to that directory:
<?php
include PEAR_PATH . '/HTML/QuickForm.php';
include PEAR_PATH . '/HTML/QuickForm/Driver.php';
...
?>
The above solution (common outside of PEAR) is not possible with files
that contain require_once as include_path must be set up properly.
Removing require_once does *not* make it harder to develop using
include_path as we are all used to and fond of, in fact it is slightly
easier. Compare:
old way:
<?php
require_once 'MDB2.php';
require_once 'HTML/QuickForm.php';
require_once 'My/Package.php';
require_once 'Another/Package.php';
?>
new way:
<?php
require 'PEAR2/Autoload.php';
?>
Finally, you can argue that the performance difference is minimal, or
even that the maintenance difference is minimal, but there is no
contending with the fact that the flexibility of the proposed solution
(removal of require_once) far exceeds that of any require_once-based
solution.
I hope that the reasoning above makes the choice of removing
require_once from PEAR2 library files a simple one.
Greg