Intervention / Intervention/image

Performance considerations

Open
#785 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
14.4k
Forks
1.5k
Avg merge
17h 51m
Merged PRs (30d)
11

Description

Hi!
I've been implementing projection conversion library. This actually means dealing with large (up to 16k x 8k) images that are compiled pixel-by-pixel by translating pixels into latitude/longitude and then back to pixels but using different translation rules. I'm accenting that because i have no other way than go on pixel-by-pixel basis, and that is part of everyday image library routine.
So, i was expecting that using library as a medium that doesn't do anything but calls to implementation i would see 2x, may be 3x processing time raise. However, my first test didn't want to end at all, so i finally came with a benchmark:

params: [128]
+--------------------------+------------+---------------+---------------+---------------+---------------+------------+
| subject                  | mem_peak   | best          | mean          | mode          | worst         | diff       |
+--------------------------+------------+---------------+---------------+---------------+---------------+------------+
| benchNativeGd            | 1,265,472b | 9,366.300μs   | 9,366.300μs   | 9,366.300μs   | 9,366.300μs   | 0.00%      |
| benchNativeImagick       | 1,265,480b | 100,052.000μs | 100,052.000μs | 100,052.000μs | 100,052.000μs | +968.21%   |
| benchInterventionGd      | 1,303,056b | 210,843.800μs | 210,843.800μs | 210,843.800μs | 210,843.800μs | +2,151.09% |
| benchInterventionImagick | 1,301,544b | 779,326.600μs | 779,326.600μs | 779,326.600μs | 779,326.600μs | +8,220.54% |
+--------------------------+------------+---------------+---------------+---------------+---------------+------------+

params: [256]
+--------------------------+------------+-----------------+-----------------+-----------------+-----------------+------------+
| subject                  | mem_peak   | best            | mean            | mode            | worst           | diff       |
+--------------------------+------------+-----------------+-----------------+-----------------+-----------------+------------+
| benchNativeGd            | 1,265,472b | 37,704.000μs    | 37,704.000μs    | 37,704.000μs    | 37,704.000μs    | 0.00%      |
| benchNativeImagick       | 1,265,480b | 410,302.100μs   | 410,302.100μs   | 410,302.100μs   | 410,302.100μs   | +988.22%   |
| benchInterventionGd      | 1,303,056b | 861,570.800μs   | 861,570.800μs   | 861,570.800μs   | 861,570.800μs   | +2,185.09% |
| benchInterventionImagick | 1,301,544b | 3,509,543.100μs | 3,509,543.100μs | 3,509,543.100μs | 3,509,543.100μs | +9,208.15% |
+--------------------------+------------+-----------------+-----------------+-----------------+-----------------+------------+

reproducing: https://github.com/etki/php-image-processing-benchmark

That's 20x slowdown for GD. CPU is doing real work not more than 5% of time. And the only real reason behind imagick being not so slowed down its that it itself is slow as hell and it's taking larger relative part of processing.

So, where did the time go? XDebug extension to the rescue

stack

In my benchmark, both setPixelAt and getPixelAt are taking roughly 50% of the time, but both drown in Image::__call (97%) and resulting in Intervention\Image\Gd\Driver->executeCommand (93%). This method creates new command and executes it; derived commands take ~35.5% of time each. That means that 21% (29%, counting callers) of time (oh my!) is lost simply on instantiating new objects, checking command names and other totally unneeded stuff. Simply deriving work to static functions where necessary would eliminate a lot of this. But it goes even worse:

irrelevant

I understand that there will always be overhead on wrapping things in library. But roughly 15% of all execution time goes to the strange Argument call, should any image processing library spend that much time on this? The real workload can't be even seen on this map, it's irrelevant to what library is really doing.

And here goes the killer part:

internals

If XDebug and KCacheGrind haven't went nuts, actual work is taking less than 0.03% of time. Now, this may be something not very accurate: you can see my call to Color::decode taking 0.11% of time (with the same amount of calls), while separate benchmark shows that imagecolorat is usually 5 times slower than Color::decode. I don't have time to dig deeper and find real figures, because estimates are already taking for themselves. Even if we multiply that 0.02% result by 100, it would take just 2% of the whole time taken.

I see this as a major library issue. I really can't use it because it spends my CPU time somewhere else but really working. I do understand the intention to wrap everything in objects and use more human-oriented approach, but there is really no necessity in wrapping simple querySomething($x, $y); call in separate object (also, diving in all the magic __call stuff was highly unpleasant). It probably also hammers all the low-level hardware optimizations brought by bright engineers: if you create an object for every low-level operation, you are abusing RAM and forcing CPU to cache thing that we be thrown right away. I, again, see this as a major issue that should be taken in consideration.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the benchmark linked in the issue and profile the mentioned paths: Image::__call, Driver->executeCommand, setPixelAt, getPixelAt, and Argument. Compare the XDebug results with the reported native GD and Imagick timings; the issue does not define a specific optimization or measurable completion target.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.