PHP: The include() include_once() performance debate

The include() include_once() performance debate

Updated with more tests on 2010-05-16.
Click here to jump to the 2010-05-16 update…

The conventional wisdom always said that PHP’s include()/require() was quicker than include_once()/require_once(), but recently I came across an interesting post by Arin Sarkissian which suggests otherwise. Also I found more commentary on the performance benefit of using relative versus absolute paths in include()/require() and include_once()/require_once() statements (although the main article’s conclusions contradict Arin’s experiments). The Drupal developers discussed and benchmarked the relative/absolute include() issue too.

So in keeping with the spirit of quick and dirty experimentation I hacked up some code and ran some tests on include()/require() against include_once()/require_once() and on the relative/absolute path issue. The results are pretty surprising and I love to hear some views.

Test Methodology

Create 10,000 uniquely named PHP files with the same content:

<?php $some_variable = "some_value"; ?>

I ran four sets if tests with the same script (slight change for PHP4 to emulate PHP5 microtime() function) on:

  • CentOS/PHP 5.1.x: CLI and Apache (with APC cache enabled). A VPS with 512MB total RAM.
  • Ubuntu/PHP 5.3.x: CLI and Apache (no APC cache). The trusty lappie 4GB total RAM.
  • Linux/PHP 5.2.x:CLI and Apache (no APC cache). A reasonably good Dreamhost shared hosting account.
  • Linux/PHP 4.4.x:CLI and Apache (no APC cache). An old highly loaded Dreamhost shared hosting account.

Each test was conducted in two runs. Each time the include()/require() operation was timed for 10,000 iterations.

  • Run 1: before any include() or require() operation each file is touch()ed.
  • Run 2: a repeat but without touch()ing the files.

Both systems had the PHP realpath cache set to the default.

Tests

require(), require_once() include(), include_once() and a dummy include_once() function:

$start = microtime(true);
for( $i=0; $i&lt;$iterations; $i++ ) { // I used 10000
    if( ! defined('ABS_INCLUDED_1_'.$i) ) {
        include( $abs_include.'_file_'.$i );
        define('ABS_INCLUDED_1_'.$i,true);
    }
}
$duration = microtime(true) - $start;

The Results

Test 1: CentOS/PHP 5.1.x: CLI and Apache (with APC cache enabled). A VPS with 512MB total RAM.

Function APACHE/Touch()d APACHE/NO-Touch() CLI/Touch()d CLI/NO-touch()
req_once( abs ) 0.521 0.529 0.505 0.507
inc_once( abs ) 0.521 0.533 0.502 0.507
inc_once( rel ) 0.681 0.694 0.651 0.661
req_once( rel ) 0.684 0.686 0.649 0.659
req( abs ) 0.811 0.841 0.670 0.681
inc( abs ) 0.869 0.830 0.716 0.687
dummy req_once( abs ) 0.907 0.889 0.717 0.718
dummy inc_once( abs ) 0.962 0.865 0.714 0.728
req( rel ) 0.983 0.982 0.836 0.839
inc( rel ) 0.983 0.981 0.851 0.851
dummy req_once( rel ) 0.993 1.021 0.889 0.912
dummy inc_once( rel ) 1.000 1.026 0.855 0.884

CentOS Results

Test 2: Ubuntu/PHP 5.3.x: CLI and Apache (no APC cache). The trusty lappie 4GB total RAM.

Function APACHE/Touch()d APACHE/NO-Touch() CLI/Touch()d CLI/NO-touch()
req_once( abs ) 0.054 0.055 0.058 0.057
inc_once( abs ) 0.055 0.055 0.059 0.057
req_once( rel ) 0.216 0.220 0.214 0.213
inc_once( rel ) 0.217 0.219 0.218 0.212
inc( abs ) 0.300 0.345 0.317 0.353
req( abs ) 0.334 0.337 0.320 0.340
dummy inc_once( abs ) 0.378 0.500 0.381 0.371
dummy req_once( abs ) 0.408 0.773 0.416 0.372
inc( rel ) 0.505 0.513 0.509 0.512
req( rel ) 0.526 0.513 0.523 0.512
dummy req_once( rel ) 0.568 1.412 0.572 1.330
dummy inc_once( rel ) 0.573 1.684 0.575 0.858
Ubuntu Results

Ubuntu Results

Test 3: Linux/PHP 5.2.x:CLI and Apache (no APC cache). A reasonably good Dreamhost shared hosting account.

Function APACHE/Touch()d APACHE/NO-Touch() CLI/Touch()d CLI/NO-touch()
req_once( abs ) 0.064 0.062 0.062 0.065
inc_once( abs ) 0.065 0.065 0.078 0.069
inc_once( rel ) 0.149 0.157 0.148 0.167
req_once( rel ) 0.155 0.147 0.146 0.155
req( abs ) 0.360 0.348 0.358 0.388
inc( abs ) 0.372 0.347 0.372 0.385
dummy inc_once( abs ) 0.406 0.390 0.402 0.425
dummy req_once( abs ) 0.408 0.392 0.408 0.428
inc( rel ) 0.422 0.394 0.408 0.439
req( rel ) 0.431 0.398 0.409 0.462
dummy inc_once( rel ) 0.444 0.449 0.523 0.495
dummy req_once( rel ) 0.448 0.440 0.520 0.482
Dreamhost / PHP5 Results

Dreamhost / PHP5 Results

Test 4: Linux/PHP 4.4.x:CLI and Apache (no APC cache). An old highly stressed Dreamhost shared hosting account.

Function APACHE/Touch()d APACHE/NO-Touch() CLI/Touch()d CLI/NO-touch()
req( abs ) 0.091 0.146 0.259 0.194
inc( abs ) 0.092 0.114 0.304 0.202
dummy req_once( abs ) 0.224 0.236 0.248 0.238
dummy inc_once( abs ) 0.233 0.263 0.343 0.236
req_once( abs ) 0.494 0.502 0.959 0.487
inc_once( abs ) 0.498 0.482 0.667 0.472
inc( rel ) 0.507 0.606 0.621 0.911
req( rel ) 0.517 0.540 0.522 0.617
req_once( rel ) 0.533 0.805 0.755 0.548
inc_once( rel ) 0.534 0.562 0.652 0.530
dummy inc_once( rel ) 0.578 0.862 0.627 0.562
dummy req_once( rel ) 0.580 0.642 0.662 0.739
Dreamhost / PHP4 Results

Dreamhost / PHP4 Results

Conclusions

While this is hardly an exhaustive test there are some interesting observations to be made:

There is a world of a difference between PHP4 and PHP5.

In fairness PHP4 has been at “End of Life” for several years now, so there is no real point in looking too closely at it.

The relative performance between include()/require() and include_once()/require_once() is markedly different between PHP versions (and that should be no surprise). In PHP4 the “conventional wisdom” certainly holds true and the include()/require() functions win out over their “once” counterparts by a significant margin. The difference is so big that the dummy include once function sometimes wins out over the native include_once() function (with absolute paths) which would suggest that those functions were not well optomised.

The difference between CLI PHP and Apache PHP follows a very different pattern in PHP5. However the servers used are not comparable and the PHP4 Dreamhost account used is so highly loaded that I wouldn’t draw conclusions from the test.

On first looks however the optimisations in PHP5 are very obvious.

For the remainder of this post I’ll ignore the PHP4 results.

include_once()/require_once() is the clear winner in PHP5

In the Ubuntu and Dreamhost tests (both of which took place on servers with plenty of RAM) there is a massive difference between include()/require() and include_once()/require_once(). I guess that the underlying filesystem caching is making a big contribution here. Even so the CentOS tests (with 512MB of RAM) still show a big difference (although that was the only test that involved the APC cache).

Avoid using relative paths in any functions

This is good practice anyway… the working directory of a PHP script can change during execution, so absolute paths are always safer to use. However the performance difference is so big that there is a performance advantage to be had. It looks like there was a lot of work done on the *_once() functions for PHP5.

I didn’t look at the difference between relative paths like “../my-folder/” and “../../my-folder”.

My disclaimer

As stated at the outset, this is pretty quick and dirty stuff and I am no guru in the underlying Apache and Linux systems. However there are some surprising numbers there and they contradict a lot of writings on the Internet (many of which would be based on PHP4).

Also I completely ignored Windows as a platform – I would be curious to see how it fares.

Most people still assume the conventional wisdom and clearly that needs a rethink.

The (not very pretty or well written) code is available to download, so have a look and see if I missed something.

Round 2

After the comments from Hal Berner & Greg Beaver below, my cusiousity was aroused enough to fire up Apache Bench (ab) and try again.

Changed Methodology

I tested the following include/require methods (see above for an explanation of the “dummy” methods):

  • require_once( /abs/path )
  • inc_once( /abs/path )
  • inc_once( ../rel/path )
  • req_once( ../rel/path )
  • dummy_inc_once( /abs/path )
  • dummy_req_once( /abs/path )
  • req( ../rel/path )
  • inc( ../rel/path )
  • inc( /abs/path )
  • req( /abs/path )
  • dummy_inc_once( ../rel/path )
  • dummy_req_once( ../rel/path )

The tests were conducted with APC cache on and then repeated with the APC cache off.

Each include/require method was tested in a separate script (with web server restarts in between tests).

Each script only included the include/require file twice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?php
/**
* PHP include/require/once/whatever
*
* The script for Greg.
*
* @author     Francis Crossen
* @see        http://www.SeeIT.org
*/


/**
* Set up...
*/

$iterations = 2;

$log_path = '';

/**
* Paths on various servers
*/

if( strpos( php_uname('n') , 'francis-laptop' ) !== false ) {
    $abs_include = '/var/www/francis/test/';
    $rel_include = '../../../../var/www/francis/test/';
}
elseif( strpos( php_uname('n') , 'ecco' ) !== false ) {
    $abs_include = '/home/francis/test/';
    $rel_include = '../../../home/francis/test/';
}
elseif( strpos( php_uname('n') , 'horologium' ) !== false ) {
    $abs_include = '/home/phil/example.com/inctest/';
    $rel_include = '../../example.com/inctest/';
}
elseif( strpos( php_uname('n') , 'seeit' ) !== false ) {
    $abs_include = '/home/example.com/domains/another-example.com/public_html/tst/';
    $rel_include = '../../../another-example.com/public_html/tst/';
    $log_path = 'files/';
}
else {

    die('Set up this script for '.php_uname('n').'!');

}

$the_test_file = 'java5.php'; // nicked from geshi

/**
* New test item
*/

$test_string = "req_once( rel )";
for( $i=0; $i&lt;$iterations; $i++ ) {
    require_once( $rel_include.$the_test_file );
}
?>

The beef is on line 51 within the “for” loop. There were 12 scripts in total (one for each test case and the only change each time was to the innards of the for loop.

This time the include file came from geshi (the “java5.php” language definition file from the “/geshi/” folder).

Apache Bench concurrency level: 5; Total number of requests: 1000.

The tests were run on a CentOS VPS, 512MB RAM. There was plenty of physical (well virtual physical) RAM available throughout the test.

There is no real load on this server – but the Apache restart would ensure that (at least at the outset) each test run (i.e. with and without APC enabled) gets a fair crach of the whip. Apache Bench was run from the command line of the server where the test took place via a bash script.

The Results

There were no failed requests or other errors.

Requests per second Time per request / ms (mean) Time per request / ms (mean, across all concurrent requests) Transfer rate / Kbytes/sec Connection Time / ms (min) Connection Time / ms (mean) Connection Time / ms (median) Connection Time / ms (+/-sd) Connection Time / ms (max)
APC req_once(abs) 212.96 23.48 4.70 39.40 4 22 82 12 999
APC inc_once(abs) 212.10 23.57 4.72 39.24 4 22 99 15 1831
APC inc_once(rel) 176.96 28.26 5.65 32.74 4 27 127 9 2009
APC req_once(rel) 154.52 32.36 6.47 28.59 4 31 133 12 2283
APC dummy_inc_once(abs) 104.03 48.06 9.61 19.25 7 47 295 9 5734
APC dummy_req_once(abs) 100.94 49.53 9.91 18.67 8 48 285 10 5038
APC req(rel) 98.98 50.52 10.10 18.31 8 49 332 10 5226
req_once(rel) 98.04 51.00 10.20 18.14 8 50 331 9 5336
req_once(abs) 96.50 51.81 10.36 17.85 9 51 372 9 6163
inc_once(rel) 95.34 52.44 10.49 17.64 8 51 356 10 5775
APC inc(rel) 94.41 52.96 10.59 17.47 7 52 353 12 5622
APC req(abs) 85.58 58.43 11.69 15.83 8 57 406 12 8067
APC inc(abs) 85.54 58.45 11.69 15.83 7 57 366 11 5706
APC dummy_inc_once(rel) 85.30 58.62 11.72 15.78 8 57 374 12 5851
APC dummy_req_once(rel) 82.45 60.65 12.13 15.25 8 59 445 10 6935
inc_once(abs) 78.52 63.68 12.74 14.53 8 62 311 13 4846
inc(abs) 51.76 96.59 19.32 9.58 16 95 582 29 9163
dummy_req_once(abs) 51.72 96.68 19.34 9.57 16 95 902 18 14972
dummy_inc_once(rel) 51.27 97.53 19.51 9.48 16 96 993 18 15931
req(abs) 50.86 98.31 19.66 9.41 16 97 793 18 14498
dummy_req_once(rel) 48.05 104.05 20.81 8.89 16 103 709 18 11156
inc(rel) 45.45 110.02 22.00 8.41 16 109 965 19 17748
req(rel) 44.78 111.65 22.33 8.28 16 110 1147 21 18415
dummy_inc_once(abs) 39.76 125.77 25.15 7.35 17 123 1000 25 20471

include-include_once-performance-ab-results

Conclusions

To my eye these results are pretty consistent with the earlier ones (although the metrics are different and the graph is not as pretty – sorry Greg!).

Further Comments

I must state that for a single include() or require() operation I would always have assumed that include_once() or require_once() would be slower. The intention here was to look at the difference between include_once()/require_once() and include()/require() when a file is included more than once in a script. While I do enjoy the banter, I don’t want to mislead people, so here is the concluding table and graph.

I repeated the Apache Bench experiments and each file was only included once.

  Requests per second Time per request / ms (mean) Time per request / ms (mean, across all concurrent requests) Transfer rate / Kbytes/sec Connection Time / ms (min) Connection Time / ms (mean) Connection Time / ms (median) Connection Time / ms (+/-sd) Connection Time / ms (max)
APC inc(abs) 227.19 22.01 4.40 42.03 3 21 71 11 938
APC req(abs) 211.91 23.60 4.72 39.20 4 22 97.8 8 1344
APC inc_once(abs) 206.08 24.26 4.85 38.13 4 23 109.4 9 1384
APC dummy_inc_once(rel) 203.88 24.52 4.91 37.72 4 23 78.5 13 994
APC req_once(abs) 203.11 24.62 4.92 37.58 4 23 103.5 9 1463
APC dummy_req_once(rel) 201.43 24.82 4.97 37.26 3 24 97.4 5 1110
APC inc(rel) 156.11 32.03 6.41 28.88 5 31 212.4 6 3267
APC dummy_inc_once(abs) 155.97 32.06 6.41 28.85 4 31 107.2 18 1196
APC dummy_req_once(abs) 154.60 32.34 6.47 28.60 4 31 167.5 12 3517
APC inc_once(rel) 153.57 32.56 6.51 28.41 3 31 137.2 12 1830
APC req(rel) 153.21 32.64 6.53 28.34 4 31 198.7 6 3130
APC req_once(rel) 152.59 32.77 6.55 28.23 5 32 131 9 1282
inc_once(rel) 102.44 48.81 9.76 18.95 8 48 378.3 9 6630
req_once(abs) 101.24 49.39 9.88 18.73 8 48 328.1 9 5370
req(rel) 98.01 51.02 10.20 18.13 8 50 334.9 9 5178
inc(abs) 97.93 51.06 10.21 18.12 8 50 401.3 9 6570
req(abs) 96.68 51.72 10.34 17.88 8 51 337.7 9 5710
dummy_inc_once(rel) 96.57 51.78 10.36 17.86 8 51 378.1 9 6426
dummy_req_once(rel) 96.02 52.07 10.41 17.76 8 51 402.8 10 6350
req_once(rel) 95.56 52.32 10.46 17.68 9 51 399.1 10 6396
inc_once(abs) 77.76 64.30 12.86 14.39 9 63 564.2 13 9349
inc(rel) 76.15 65.66 13.13 14.09 8 65 460.4 13 6796
dummy_req_once(abs) 74.25 67.34 13.47 13.74 8 66 562.1 13 8969
dummy_inc_once(abs) 71.72 69.72 13.94 13.27 8 68 602.7 13 10117

Graph of results - one include()/require()/include_once()/require_once()

With a single include() or require() operation it should come as no surprise that include() wins out over require(). While the figures without APC cache would suggest otherwise, the difference is so small that I would hesitate to draw any conclusion from it. Even though the Apache process was restarted between tests, the VPS was still in use during the test, albeit at a low load.

The difference between the relative and absolute path as a parameter to the include()/require() statement is still there although there appear to be some anomalies. For example:
why does the dummy include_once( ../relative/path ) and dummy require_once( ../relative/path ) appear to be quicker than using /absolute/paths?

If you were so inclined it would be interesting to run these tests without APC on a virtualised machine that is not doing anything else. Regardless APC rocks and its’ inclusion in a future release of PHP is very welcome.

Personally I prefer to use a single include() statement whenever possible and to encapsulate everything I need in a function or class. That removes the need to use include_once(): a second attempt to include() will throw an error because the class or function is already defined. This approach also keeps code cleaner and therefore more maintainable.

Tagged with: , , ,
Posted in Servers, Tech Blog, Web
9 comments on “PHP: The include() include_once() performance debate
  1. Well, this is certainly something to look at and play around with. I have been avoiding require_once/include_once where I could based on assumptions gained from reading (probably the same) information online.

    Essentially, anytime I use an __autoload() function I avoid using require_once or include_once since we know that if the file had been included already we wouldn’t have fallen into the __autoload(). However, if it is faster, as it seems here, it may be worth it.

  2. Hal Berner says:

    If I’m not mistaken, that benchmark measures how long it takes to run 10,000 include in succession, right? It doesn’t seem to bear much relevance to real-world scenarios. How about some ab/siege/whatever test on 10,000 HTTP requests, each running 1 include? Or rather 10 include’s on different files, because it seems like a more likely scenario.

    By the way, try replacing your for loops with do{}while (–$i); where $i is initialised with the number of iterations. For some reason, do-while incurs much less overhead than for and still less than while. Or so it seems.

  3. Greg Beaver says:

    Hi,

    First of all, you can’t benchmark anything properly using microtime() in PHP, the only way to accurately benchmark is to use siege, or its slightly less accurate cousin apache benchmark (ab)

    I would also like to point out that no one is including a file over and over again that contains a single variable assignment. People are including libraries with class or function code. If you want to see a benchmark of ways of including code that also addresses the comparison of including versus stuffing it all into one file (and this one against PHP 5.3 in its pre-alpha stages), try

    http://markmail.org/message/hxzqcgrzrh6szce4

    for a sample. Disclaimer: I haven’t tried this benchmark in a few years, and there have been some further optimizations of PHP itself, and of how APC handles *_once. However, the performance difference between include and include_once is not the only reason to avoid using require_once/include_once, tangled circular *_once references (like in the HTML_QuickForm package) can render a library unoptimizable by APC.

    I think you’ll find that your numbers are less surprising when it turns out that they are wrong :)

  4. Hari K T says:

    This can be a good experiment to test the servers ;)

  5. Jaik Dean says:

    Surely you should just use whichever function is relevant in each situation? If you’re getting any tangible real-world performance benefit from switching between these functions then I feel your script probably has some much more serious issues that need addressing before such micro-optimisations.

  6. Francis says:

    @Hal / @Greg:

    I agree – the test set up is totally artificial. However I wanted to look at the PHP functions themselves rather than start benchmarking the Apache server. The tests on http://markmail.org/message/hxzqcgrzrh6szce4 would be interesting to revisit today.

    @Jaik:

    I wouldn’t be too bothered with the performance differences myself – as Greg points out including the same trivial file 10,000 times hardly demonstrates anything tangible. I prefer to code in such a way that a file only ever needs to be included once anyway (by encapsulating in a class or a function).

    @Sjan:

    I didn’t look at __autoload() for the simple reason that I avoid using it. I did use it earlier in the Tina framework, but I found that I ended up colliding with another plugin author’s __autoload() function. In an environment where everyone is playing nicely with each other I would like to use __autoload(), but in WordPress that doesn’t happen!

  7. Greg Beaver says:

    1) If you read the benchmark carefully, you can see that in fact what you want to compare is not the apache server, but the *difference* between the speeds of PHP using the same apache setup. Note that in each case, a single request is made to the apache server. In other words, because all of the tests use the same apache setup, and the apache server is polled more than 10,000 times over the course of a minute, the measured difference can be reliably attributed to differences in PHP execution speed, and not in any quirks of apache.
    2) include/require are not functions, and if you are executing the same code over and over and over again with a single file, it will hit the realpath cache, and some other optimizations. You benchmark is not just synthetic, it has absolutely nothing to do with real-world performance of include/require.

    Why does this even matter? You make some assertions with fancy charts, and then proceed to recommend that people actually try what is empirically the slowest options. Please re-run the benchmarks using proper benchmarking methodology and you will see the drastic errors in your ways.

    For a start, perhaps you could steal Paul M. Jones’s script for benchmarking different frameworks:

    http://code.google.com/p/web-framework-benchmarks/source/browse/trunk/_siege/siege.php

    Related reading:

    http://paul-m-jones.com/archives/421

  8. Francis says:

    @Hal / @Greg:

    In fairness the intention was to see how quick an include_once()/require_once() is compared to an include()/require() statement when the statement is including the same file more than once – perhaps I should have been more clear about that at the outset. I wouldn’t assume that a single include_once()/require_once() statement is quicker than a single include()/require() statement.

    But fair enough – I’ve taken the bait and tweaked the tests – results are above. This time each file was only included/required twice and Apache Bench was used to invoke the actual scripts.

    @Greg:

    Why does this matter? Well, I’d rather know than guess and the debate has been around for years. My conclusions are of course subjective, and (fancy charts notwithstanding) the empirical evidence is that include_once() and require_once() are the quickest – not the slowest.

    My conclusions (the use of relative paths and that require_once()/include_once)() are quicker) are my own and I would hardly call them recommendations.

    @Everyone:

    What about some code to contradict/confirm these findings?

  9. nice post!

    from my experience with tight optimization, autoload is not always fastest either. if you include some files for all requests (or 98% of them) its faster to do it with a bunch of req ince with abs path.

    also difference between abs and rel increases when you have more dirs on the include path .. that is tricky as depends on env so better check prod setup ;) … could be worth checking the numbers :)

    cheers

2 Pings/Trackbacks for "PHP: The include() include_once() performance debate"
  1. [...] wise it looks like using either of the _once functions is faster. Include beats out require. So if you are looking to squeeze out every little bit of performance [...]

  2. [...] wise it looks like using either of theĀ _once functions is faster. Include beats out require. So if you are looking to squeeze out every little bit of performance [...]

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>