Problem/Motivation
Have been meaning to open this issue for years now. Latest reminder was bojanz linking to https://github.com/composer/composer/pull/5174#issuecomment-208824818
The apcu classloader from Symfony uses an entry-per-class. This means lots of sets and gets on each request. Also APCu doesn't handle LRU, or getting full very well, so it's prone to fragmentation.
Proposed resolution
Add an alternative APCu classloader which maintains a single entry. The main challenge is to avoid write-stampedes. Fetching and writing at the end of the request, in a similar way to CacheCollector would help vs. that.
Also need to be careful that the array size doesn't get ridiculous.
Remaining tasks
User interface changes
API changes
Data model changes
Comments
Comment #2
dawehnerLet's try that out. This is a really naive implementation
Comment #3
catchNot sure we need much more than that, except this isn't going to work ;) Probably needs a protected $writeCache; for this.
Comment #4
dawehnerEspecially the boolean logic is broken :)
Comment #5
dawehnerAlright, here is a more working implementation :P
Comment #7
anavarreComment #8
catchProbably worth doing a get -> merge -> set here?
Comment #11
dawehnerThis is really a strong point, especially when it comes down to plugin/controller classes.
Comment #13
dawehnerThis just fixes the last failure.
Comment #14
catchThis looks great, thanks for keeping it going!
Two comments on comments, otherwise looks ready to me.
For profiling I think we'd just want to see one get and one set per request, and a request with a warm cache that does not do a set? It would be interesting to know how large the items gets after clicking around admin pages a bit maybe too.
APC or APCU now?
Do we need a comment here to say we're not using strict comparison because we don't care about array order?
Comment #15
catchOne positive for this patch from #2926309: Random fail due to APCu not being able to allocate memory - it will significantly reduce the number of entries, which means default APCu settings might be a bit closer to reality.
Comment #18
andypostComment #19
andypostMaybe better to move this to `Drupal\Core` namespace? otherwise this new component needs composer.json
Comment #20
andypostPatch with fixed comments for #14 based on reroll for 8.7.x
decorated could return FALSE or NULL when file not found so probably better to normalize it
For example
\Composer\Autoload\ClassLoader::findFile()returnsFALSEComment #22
dawehnerThis feels like a patch which ideally someone actually battle-tests in a live environment.
Comment #23
andypost@dawehner yep, that's why I rerolled to get real numbers on project - at least it removes 8k calls to
apcu_fetch()but I need to get the RAM effectComment #24
ndobromirov commentedI am concerned about initialization. If the key is missing, how can this initialize without triggering warnings? On first call we will end with
array_merge(array, false). The negative case should be handled with a sane default for$previous_classmap- an empty array.Comment #25
gábor hojtsyAdding to the Drupal 9 issue tree, since this may need to be done as part of moving to Symfony 4 as per #3020296: Remove Symfony's classloader as it does not exist in Symfony 4.
Comment #27
dawehnerComment #29
yogeshmpawarAll looks good so setting back to Needs Review! Tests failure is not related (see #3055648: Frequent random fail in \Drupal\Tests\media_library\FunctionalJavascript\MediaLibraryTest).
Comment #31
bradjones1Comment #32
catchThis is one of two classloader options for unblocking Drupal 9's release, so needs to stay tracked against 9.0.x and could still potentially be backported to 8.9.x. See #3020296: Remove Symfony's classloader as it does not exist in Symfony 4.
Comment #33
bradjones1Thanks for the clarification and sorry for the noise. I'm trying to digest these in context of wrapping my head around an option for preloading in #3108687: Compatibility with/take advantage of code preloading in PHP 7.4 so doing some cleanup on some older tickets as I go. I'll try to help with these since it would be great not to have a delay on 9.0.
Comment #34
bradjones1Re-rolled for a small bit of fuzz against 9.0.x.
Comment #36
bradjones1The failure is an assertion after module uninstallation of
help_topics... the test was added in October, so after the last pass. I'll have to manually test to see if this is a fluke or related somehow.Comment #37
gappleThe logic here doesn't look right - my understanding is that if the classmap is not already initialized, and the requested class does not already exist in the cached data, then it will skip the call to the decorated class
@ndobromirov raised concern that if the classmap wasn't previously cached this would raise a warning due to calling
array_merge(array, false), but I think the conditional will prevent the data from ever being cached, since fetch will return FALSE.Comment #38
gappleAlso, I think since the classmap is initialized to an empty array
Then the
issetcheck will always return TRUEand data will never be fetched from APCu.
Comment #39
gappleFixes for my previous comments, plus a test to check that a repeated path lookup uses cached data.
Comment #40
gappleAn important consideration is that if composer's caching is enabled, then it will cache each class lookup individually as well as this decorator's single cache entry.
- This decorator could be only enabled in
DrupalKernel::initializeSettings()if composer's caching is not- or Composer's caching could be disabled in favour of Drupal's
Comment #45
nod_I'm getting a little off-track but when I saw various xdebug profiles runs for a stock D9 site, the acpu thing jump at me and I've been thinking about the idea of bundling PHP classes that are dependent/related in a single file, like we'd bundle js code.
This would be more of a composer-level optimisation (like in the dump-autoload command). Had the idea seeing the symfony code where there are many class_exists all over the place to "optimize" loading, for example the
HttpKernel.phphas:A whole dependency graph would be needed but since it's done at composer time that might be acceptable in term of time/memory consumption. The file above would bundle everything only used by this class (using php multiple namespace feature):
This should reduce the number of separate file needed (don't have a measure of how much it would help for now, didn't spend too much time on this). I mean if it looks good, we might not need to do much on the acpu front. There were a couple of strange things going on, I think the "use" are global within the file so if we use different class with the same name in different namespace it kinda breaks but it should be possible to find a solution.
Comment #46
berdirSounds like you're looking for preloading? See #3108687: Compatibility with/take advantage of code preloading in PHP 7.4
Comment #49
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #50
bkosborneWith #3020296: Remove Symfony's classloader as it does not exist in Symfony 4 done, is this issue still relevant?
Comment #51
catch@bkosborne yes the composer classloader uses APCu entry per file, and because modules can be installed and uninstalled, we can't rely on the compiled container.