vlucas / phpdotenv

Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.
BSD 3-Clause "New" or "Revised" License
13.11k stars 623 forks source link

High performance impact on Parser #510

Closed deleugpn closed 1 year ago

deleugpn commented 2 years ago

I have a Laravel project with about 51 environment variables on our .env.testing file. The project consist of about 1300 automation tests written on PHPUnit.

I was investigating a performance issue on our test suite and I managed to pinpoint one major degradation to https://github.com/vlucas/phpdotenv/blob/master/src/Parser/Parser.php#L27

The way I "benchmarked" this analyses was by adding a throw new SkippedTestError('Skip'); and running the entire test suite on different places. Here is the result before that line is executed:

       $var = Regex::split("/(\r\n|\n|\r)/", $content)->mapError(static function () {
            return 'Could not split into separate lines.';
        });

        throw new SkippedTestError('Skip');

        $var = $var->flatMap(static function (array $lines) {
            return self::process(Lines::process($lines));
        });

        $var =  $var->mapError(static function (string $error) {
            throw new InvalidFileException(\sprintf('Failed to parse dotenv file. %s', $error));
        })->success();

        return $var->get();
bash-4.2# ./laravel/vendor/bin/phpunit
PHPUnit 9.5.16 by Sebastian Bergmann and contributors.

SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS   61 / 1358 (  4%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  122 / 1358 (  8%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  183 / 1358 ( 13%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  244 / 1358 ( 17%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  305 / 1358 ( 22%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  366 / 1358 ( 26%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  427 / 1358 ( 31%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  488 / 1358 ( 35%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  549 / 1358 ( 40%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  610 / 1358 ( 44%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  671 / 1358 ( 49%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  732 / 1358 ( 53%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  793 / 1358 ( 58%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  854 / 1358 ( 62%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  915 / 1358 ( 67%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  976 / 1358 ( 71%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1037 / 1358 ( 76%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1098 / 1358 ( 80%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1159 / 1358 ( 85%)
SSSSSSSSSSSSSSSSSSSS.SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1220 / 1358 ( 89%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1281 / 1358 ( 94%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1342 / 1358 ( 98%)
SSSSSSSSSSSSSSSS                                              1358 / 1358 (100%)

Time: 00:03.158, Memory: 174.00 MB

It doesn't matter how many times I run the suite, it always finishes below 3.5 seconds.

Now moving the exception one step below:

        $var = Regex::split("/(\r\n|\n|\r)/", $content)->mapError(static function () {
            return 'Could not split into separate lines.';
        });

        $var = $var->flatMap(static function (array $lines) {
            return self::process(Lines::process($lines));
        });

        throw new SkippedTestError('Skip');
bash-4.2# ./laravel/vendor/bin/phpunit
PHPUnit 9.5.16 by Sebastian Bergmann and contributors.

SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS   61 / 1358 (  4%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  122 / 1358 (  8%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  183 / 1358 ( 13%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  244 / 1358 ( 17%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  305 / 1358 ( 22%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  366 / 1358 ( 26%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  427 / 1358 ( 31%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  488 / 1358 ( 35%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  549 / 1358 ( 40%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  610 / 1358 ( 44%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  671 / 1358 ( 49%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  732 / 1358 ( 53%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  793 / 1358 ( 58%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  854 / 1358 ( 62%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  915 / 1358 ( 67%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS  976 / 1358 ( 71%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1037 / 1358 ( 76%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1098 / 1358 ( 80%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1159 / 1358 ( 85%)
SSSSSSSSSSSSSSSSSSSS.SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1220 / 1358 ( 89%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1281 / 1358 ( 94%)
SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS 1342 / 1358 ( 98%)
SSSSSSSSSSSSSSSS                                              1358 / 1358 (100%)

Time: 00:06.796, Memory: 172.00 MB

It doesn't matter how many times I run the tests, it always end up above 6 seconds.

I'm still investigating if there is anything I can do on my side to mitigate this or if there is anything I can try to propose to the lib to improve this, but for now I thought I'd open an issue in case anybody have any bright ideas.

GrahamCampbell commented 2 years ago

I'd not recommended to parse the file over and over. Use config caching. :)

GrahamCampbell commented 2 years ago

The line you link to is the line that basically does all of the parsing, btw. ;)

deleugpn commented 2 years ago

Should I be using caching for PHPUnit? Is that really the recommendation?

GrahamCampbell commented 2 years ago

Yes, definitely. Run both config and route cache, with the env set to testing.