Performance issue with the carbon_compat.php autoloader
odan opened this issue · comments
With performance measurements (Apache ab), I found out that once you add Chronos to any project using composer require cakephp/chronos
the overall Composer autoloading performance decreases by up to 50%. This has a huge impact on the overall performance of the web application. So I checked where exactly it is so slow. The reason is the file "src/carbon_compat.php".
Specifically, those two lines are what cause performance issues for the optimized composer autoloader.
https://github.com/cakephp/chronos/blob/2.x/src/carbon_compat.php#L21-L22
For testing purposes, I deleted the contents of this file (locally) and found that the performance then turned to normal.
Xdebug was disabled.
composer.json
{
"name": "test/test",
"type": "project",
"description": "Test",
"require": {
"cakephp/chronos": "^2"
}
}
index.php
require_once __DIR__ . '/vendor/autoload.php';
composer update --no-dev -o
Apache ab:
ab -n 500 -c 100 -k http://localhost/test/index.php
Is there any way to fix this?
Edit: My current workaround to fix this performance issue is to add a pseudo Carbon class before the actual composer autoloader:
require_once __DIR__ . '/carbon.php';
require_once __DIR__ . '/vendor/autoload.php';
carbon.php
<?php
namespace Carbon;
class Carbon
{
}
Using the composer autoload > files
section for this file does not work, because composer will load the Chronos file src/carbon_compat.php
before it loads the project specific autoloaders. It is also not possible to exclude files from autoload_files.php
I'm sure there must be a better way to prevent this carbon_compat.php
file from loading. Any ideas?
Interesting that the additional class_exists()
checks are so expensive. Usually those kinds of checks are low cost.
I wonder if using the class_exists($class, false)
form would help with performance issues?
Can you show us the the performance numbers?
Here are some numbers.
Used commands per test:
composer update --no-dev -o
ab -n 5000 -c 100 -k http://localhost/chronos-demo/index.php
System: PHP 8.0.1, XDebug is disabled
Requests per second [#/sec] (mean) - (More is better)
- Empty index.php file: 9697
- With
vendor/autoload.php
, and no dependencies in composer.json: 4564
Note: I never expected that composer itself has such an impact.
- composer.json with
"cakephp/chronos": "^2.3"
as single dependency: 1345
As you can see, the difference between test 2 and test 3 is quite significant.
- Then I tested it with
class_exists($class, false)
:
if (!\class_exists('Carbon\Carbon', false) && !\interface_exists('Carbon\CarbonInterface', false)) {
class_alias('Cake\Chronos\Chronos', 'Carbon\MutableDateTime');
class_alias('Cake\Chronos\ChronosInterface', 'Carbon\CarbonInterface');
}
Result with class_exists($class, false)
: 1333
You see, there is no real performance difference using false
here.
- Then I removed the
class_alias
function calls insrc/carbon_compat.php
:
if (!\class_exists('Carbon\Carbon') && !\interface_exists('Carbon\CarbonInterface', false)) {
}
Result without class_alias
: 3764
This looks quite good now.
- Then I cleared the file
src/carbon_compat.php
completely.
Result with empty file (without class_exists and without class_alias): 3855 (wow!)
This means that class_alias
is the most problematic function call from a performance perspective:
class_alias('Cake\Chronos\Chronos', 'Carbon\MutableDateTime');
class_alias('Cake\Chronos\ChronosInterface', 'Carbon\CarbonInterface');
Have you performed tests using composer with other dependencies (with and without chronos added)?
@othercorey Yes, I have also tested other packages. Here are some results.
Result is [requests/sec]. More is better.
- No dependencies: 4564 (maximum)
cakephp/database
: 2302cakephp/validation
: 2519symfony/console
: 1893 (also very slow)symfony/uid
: 3265fig/http-message-util
: 4256monolog/monolog
: 3764slim/slim
: 3368stripe/stripe-php
: 3168
Combinations:
cakephp/chronos
+cakephp/validation
+cakephp/database
: 1000
I'm not sure what the expectation here is other than composer should be as fast as not using composer.
Sorry, I guess my actual question was not clear.
I don't need the Carbon class alias, and it turns out that class_alias
in src/carbon_compat.php
slows down the composer autoloader around 5 to 10 ms per http request.
My question was, how can I turn off or disable the Carbon class alias to get more performance?
I suggest a simple check for a special constant may help here:
if (
!\defined('CHRONOS_CARBON_COMPAT_OFF') &&
!\class_exists('Carbon\Carbon') &&
!\interface_exists('Carbon\CarbonInterface', false)) {
\class_alias('Cake\Chronos\Chronos', 'Carbon\MutableDateTime');
\class_alias('Cake\Chronos\ChronosInterface', 'Carbon\CarbonInterface');
}
To disable the Carbon alias, I could then define this constant before loading the composer autoload.php file to disable this feature.
define('CHRONOS_CARBON_COMPAT_OFF', 1);
require_once __DIR__ . '/vendor/autoload.php';
My question was, how can I turn off or disable the Carbon class alias to get more performance?
You can't. Adding the stub class to prevent the class_alias
calls is your best bet. You'll find other class_alias
usage in CakePHP that can't be removed as easily though.
I suggest a simple check for a special constant may help here:
I guess that's an option. We'll likely remove the class_aliasing in a future major release. An additional constant would provide a short term solution though.
Ok, thanks for all your feedback and critical questions. I think I'll wait until this alias is completely removed in the future. Until then, I will probably be able to solve it with the class stub.