cakephp / chronos

A standalone DateTime library originally based off of Carbon

Home Page:http://book.cakephp.org/chronos

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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)

  1. Empty index.php file: 9697
  2. With vendor/autoload.php, and no dependencies in composer.json: 4564

Note: I never expected that composer itself has such an impact.

  1. 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.

  1. 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.

  1. Then I removed the class_alias function calls in src/carbon_compat.php:
if (!\class_exists('Carbon\Carbon') && !\interface_exists('Carbon\CarbonInterface', false)) {
}

Result without class_alias: 3764

This looks quite good now.

  1. 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: 2302
  • cakephp/validation: 2519
  • symfony/console: 1893 (also very slow)
  • symfony/uid: 3265
  • fig/http-message-util: 4256
  • monolog/monolog: 3764
  • slim/slim: 3368
  • stripe/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.