php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #79572 The builtin autoloader function spl_autoload() does not use opcache
Submitted: 2020-05-07 14:14 UTC Modified: 2020-05-07 18:02 UTC
From: czirkos dot zoltan at gmail dot com Assigned:
Status: Open Package: SPL related
PHP Version: 7.2.30 OS: Linux 4.15.0-99-generic
Private report: No CVE-ID: None
 [2020-05-07 14:14 UTC] czirkos dot zoltan at gmail dot com
Description:
------------
PHP 7.2.30-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Apr 19 2020 07:50:50) ( NTS )

When the builtin autoloader loads a class, it will not use opcache.
It will always look for the file in the filesystem, regardless of
its status.

This causes performance degradation in a web server setting (eg. fpm).

I reproduced the problem in the cli sapi for easy testing, test
results and example files are below. Even though using opcache for
cli is not realistic, I simulated loading the class multiple times
by calling spl_autoload repeatedly. Please note that the problem
CAN BE REPRODUCED IN FPM by strace-ing the running fpm process as well.


Example files:

    loadedclass.php:
    
    <?php   /* just an empty file */


    test_spl.php:
    
    <?php
    set_include_path(__DIR__);
    spl_autoload_extensions(".class.php");
    foreach (range(1, 10000) as $i)
        spl_autoload('loadedclass');


    test_simple.php:
    
    <?php
    set_include_path(__DIR__);
    function my_autoload($name) {
        include $name . ".class.php";
    }
    foreach (range(1, 10000) as $i)
        my_autoload('loadedclass');


The file test_simple.php contains an autoload function which is
almost equivalent to 'spl_autoload'. Note that it does NOT use include_once,
but it uses the simple include command, this is important.


With opcache turned OFF (opcache.enable = 0, opcache.enable_cli = 0):

    strace -c php test_spl.php 
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     45.78    0.311463           8     40174           fstat
     14.87    0.101157          10     10169         3 openat
     13.46    0.091557           9     10313           mmap
     12.96    0.088142           9     10151           munmap
     12.15    0.082671           8     10170           close

Calling spl_autoload 10000 times shows tha the file is opened
10000 times (I don't know why it is fstat'ed 40000 times).

The other code produces the same results, as expected:

    strace -c php test_simple.php 
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     45.00    0.297444           7     40174           fstat
     14.95    0.098808          10     10169         3 openat
     14.04    0.092786           9     10151           munmap
     13.47    0.089016           9     10313           mmap
     11.93    0.078883           8     10170           close


However with opcache turned ON (opcache.enable = 1, opcache.enable_cli = 1):

    strace -c php test_spl.php 
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     45.58    0.317274           8     40175           fstat
     14.91    0.103765          10     10170         3 openat
     13.52    0.094119           9     10315         1 mmap
     12.90    0.089784           9     10152           munmap
     12.12    0.084343           8     10171           close

For the spl autoloader, the file is still opened 10000 times! As
these are openat() and mmap() kernel calls, this means that the file
is also loaded and compiled. There is no speedup in the application.

However, with the hand-made autoloader, the head of the strace
looks like this:

    strace -c php test_simple.php 
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     20.44    0.002377           8       316         1 mmap
     16.11    0.001873           8       230           mprotect
     12.28    0.001428           8       171         3 openat
      9.08    0.001056           6       172           close
      8.81    0.001025           6       179           fstat

Execution time is almost zero, there are no 10000 file opens and loads.


The same thing happens in the fpm setting. For my application with
around 500 classes loaded, Apache benchmark shows 12-13 ms / page
load when I'm using the spl_autoload for loading the classes, and
9-10 ms / page load when I'm using the simple autoload function above.

By stracing the php7.2-fpm worker process, I get the same results:
when using spl_autoload, there are many openat() kernel calls,
but when using the simple autoload function, there openat() and
the fstat() calls disappear.


Test script:
---------------
# loadedclass.class.php
    # empty file


# test_simple.php
set_include_path(__DIR__);
function my_autoload($name) {
    include $name . ".class.php";
}
foreach (range(1, 10000) as $i)
    my_autoload('loadedclass');


# test_spl.php
set_include_path(__DIR__);
spl_autoload_extensions(".class.php");
foreach (range(1, 10000) as $i)
    spl_autoload('loadedclass');



Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2020-05-07 15:16 UTC] laruence@php.net
spl_autoload use php_stream_open_for_zend_ex directly, it will always open the file, it should use zend_stream_open instead, anyway, I am wondering if there is any real usecases of spl_autoload in realworld (most uses own autoloader like PSR-4)?
 [2020-05-07 17:51 UTC] nikic@php.net
Maybe we should deprecate this functionality for PHP 8. Even if you don't want to use a composer autoloader, a manual implementation is 3 lines of code. The default spl_autoload() behavior is completely non-standard nowadays (especially the lower casing of class names).
 [2020-05-07 18:02 UTC] czirkos dot zoltan at gmail dot com
Yes, deprecating can also solve this problem. Currently I use this instead:

spl_autoload_register(function($class) {
    @include __DIR__ . "/" . strtr($class, "\\", DIRECTORY_SEPARATOR) . ".class.php";
});

Exactly 3 lines. The documentation of spl_autoload_register could also provide something like this as an example. Of course, the semantics of that function would then change as well, as the $callable argument would become mandatory.
 [2020-05-07 18:05 UTC] bugreports at gmail dot com
> Maybe we should deprecate this functionality for PHP 8
> Even if you don't want to use a composer autoloader
> a manual implementation is 3 lines of code

why can't it just do the same as include / require like spl_autoload_register() registered functions behind the scenes?

i mean why is tehre more than one code path loading / caching php scripts at all in the engine?
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sun Nov 03 17:01:27 2024 UTC