Re: require_once vs. no require_once - please read, critical information

From: Date: Sun, 23 Sep 2007 23:53:49 +0000
Subject: Re: require_once vs. no require_once - please read, critical information
References: 1  Groups: php.pear.dev 
Request: Send a blank email to pear-dev+get-48141@lists.php.net to get a copy of this message
While I don't want to re-hash the old arguments - this again concludes on very thin evidence same same rather faulty conclusions. - Maintainability - having a description of where a class comes from near where it is used is always going to be simpler, no mater what you come up with. - Performance improvements, are negligible compared to that of a real life application. I have no idea why you can't create a package, PEAR2_StripRequires which removes all the requires and let's you use autoload or allfiles. If someone want's to try this out, and see all the long term maintenance problems they will end up with, let them, but please dont force it on everyone.. Regards Alan Gregory Beaver wrote:
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


« previous php.pear.dev (#48141) next »