Problem/Motivation
Symfony's UniversalClassLoader uses artificial distinction between namespaced classes and unnamespaced ones. We're using PSR-0, which does not distinguish between the two.
Proposed resolution
Switch to Symfony's ClassLoader which directly advertises PSR-0 support, which does not differentiate between namespaces classes and unnamespaced ones. This cleans up a bunch of the class loader code.
Remaining tasks
The patch is RTBC, and needs to be committed.
User interface changes
None.
API changes
drupal_classloader()
now provides a ClassLoader object.
Original report by chx
https://github.com/doctrine/common/pull/154#discussion_r1040649
UniversalClassLoader was adding an artificial distinction between namespaced classes and unnamespaced ones whereas PSR-0 does not. And Symfony 2.1 introduced a ClassLoader which has a simpler API by avoiding this distinction (just like Composer avoids it too)
Comment | File | Size | Author |
---|---|---|---|
#107 | UniversalClassLoader.png | 34 KB | RobLoach |
#107 | ClassLoader.png | 29.26 KB | RobLoach |
#104 | interdiff.txt | 1.97 KB | dawehner |
#103 | 1658720.patch | 15.04 KB | RobLoach |
#102 | drupal-1658720-102.patch | 13.75 KB | dawehner |
Comments
Comment #1
RobLoachUsing straight up ClassLoader makes complete sense here.
The APC ClassLoader is removed as it doesn't work with what we have (
drupal_classloader_register()
gives an exception). In replacement of it, once generate classmaps for all PSR-0 packages hits, we should switch to the Composer ClassLoader as there would be a 1:1 classmap given to us. This would make it extremely fast as we wouldn't even need to check the PSR-0 namespaces. This must follow after #1424924: Use Composer for updating Symfony components (without removing Symfony code from repo) hits though.In the mean time, we're using Symfony's straight up ClassLoader without its APC counterpart.
Comment #2
RobLoachForgot to remove a line.
Comment #3
Crell CreditAttribution: Crell commentedI think we decided on a coding standard of not including the \\ in single-quoted class name strings, didn't we?
Aside from that quibble, I'm fine here.
Comment #4
RobLoachTypo.
Comment #5
RobLoachUpdated
Comment #6
RobLoachLet's post-pone this one until #1424924: Use Composer for updating Symfony components (without removing Symfony code from repo) gets in. Angie wanted to bring the needs review threshold down.
Comment #7
chx CreditAttribution: chx commented12 insertions(+), 37 deletions(-). That's some some serious simplification right there. I love this patch. It also eats up at least #1628040: Univerclassloader fallbacks are not namespaced and #1620114: Explicitly register \Drupal\Component and \Drupal\Core to increase class lookup performance
Comment #8
webchickShould we do some benchmarks here?
Comment #9
RobLoachThey'd be along the same lines as the benchmarks catch did in Explicitly register \Drupal\Component and \Drupal\Core:
before.png
after.png
Comment #10
chx CreditAttribution: chx commentedThere is no need to wait on the Composer patch with this one. However, yes, new benchmarks are in order because despite the benefit of not using fallback there is one str_replace added per findFile which was not there before because UniversalClassLoader made a distinction between namespace and pear like class names which is now gone so the replace underscore to dirseparator always happens.
@RobLoach, Symfony is PHP code. I heartily recommend reading it before following up on an issue. This is the second issue I am following up on just today where you have followed up on an issue without reading the relevant Symfony code. I am sorry I need to write this. This is not meant to be a personal attack at all.
Comment #11
RobLoachThis needs work as we might actually be able to make ApcClassLoader work for us here.
Comment #12
RobLoachComment #13
cweagansPatch looks pretty straightforward and I'm inclined to RTBC it, but it sounds like we want to see some benchmarks first, so tagging.
Comment #14
cweagansRelated: #1807662: Built-in APCu support in core (PHP 5.5 only)
Comment #15
RobLoach#1620114: Explicitly register \Drupal\Component and \Drupal\Core to increase class lookup performance first :-)
Comment #16
amateescu CreditAttribution: amateescu commented#1620114: Explicitly register \Drupal\Component and \Drupal\Core to increase class lookup performance landed, the drop is moving so fast :)
Comment #17
dawehnerAdding a tag
Let's rerole the patch against recent changes in this part.
Comment #19
dawehnerOh right, annotations have to be converted as well to use the new namespaces
Comment #20
sunLooks good to me.
Unless we need benchmarks here (I doubt it?), this should be ready to fly.
Comment #21
dawehnerI'm not sure whether this is just random fluctuation (running 100 requests and aggregated here).
This is not a normal drupal page you see here, one with pretty heavy class usage.
Comment #22
dawehnerNote: this patch IS using the APCClassLoader part, see #1658720-1: Use ClassLoader instead of UniversalClassLoader, maybe robloach should review as well before getting this in.
Comment #23
sun1) Hm. So ClassLoader is minimally/slightly slower, and involves more function calls.
2) I looked at the ApcClassLoader docs and the changes in this patch seem to be correct.
Comment #24
RobLoachPutting the benchmarks aside, it looks like we're instantiating a ClassLoader twice. Once using "ClassLoader", and then again later on if it's APC.
Comment #25
sunYes, that's how ApcClassLoader is supposed to be used. Make sure to read its docs:
http://drupalcode.org/project/drupal.git/blob/HEAD:/core/vendor/symfony/...
Comment #26
RobLoachAh, thanks!
Comment #27
Damien Tournoud CreditAttribution: Damien Tournoud commentedTwig doesn't use PSR-0 classes, so are we sure that we can use the non-universal class loader here?
Comment #28
RobLoachPSR-0 covers PEAR-like autoloading as it replaces any "_" in the class name with a directory separator for the class's path. You can see that on line 181:
In Twig's composer.json, you can see it asks for the Composer's PSR-0 ClassLoader, which pretty much does the same thing. The benefit of using Symfony's straight up ClassLoader is that Symfony's ClassLoader component comes with the APC one. Composer's performance optimization comes in with
composer dump-autoload --optimize
, which caches a straight up classmap array, which would help even APC. But, that's a separate issue: #1818628: Use Composer's optimized ClassLoader for Core/Component classes.Comment #29
Fabianx CreditAttribution: Fabianx commentedMore benchmarks:
First benchmark is baseline, which shows some slight variance here of 179 micro secs.
144 function calls more, but the overhead is 224 micro secs plus, which is within variance, I think this is fine. The overhead are more calls to strpos (135, besides that CL uses file_exists instead of is_file, but that makes no real difference. file_exists is also called 5 times more in this benchmark.
I think this is good to go!
Comment #30
Fabianx CreditAttribution: Fabianx commentedTested with
Got:
Please fix.
Comment #31
RobLoachMoved over to #1834594: Update dependencies (Symfony and Twig) follow up fixes for Composer.
Comment #32
sunThe classloader change should happen here. It doesn't make sense to do it in that issue, because as you can obviously see, it requires discussion, reviews, and benchmarks.
Comment #33
Crell CreditAttribution: Crell commentedFor the record: Performance-tweaking about our class loader right now is an epic waste of time. We can, by design, swap our class loader at any time without impacting functionality. We could do so post-freeze, post-code-freeze, hell we could do it in June or July or August and it would be fine. Right now the focus should be on easy, not micro-optimized. Do what makes development each, and stuff the rest. Revisit before release. Done.
Composer's class loader "just works" without much additional effort, and we're still adding libraries through it and updating others. Using that for now makes that process easier. Easy trumps optimized for the next 3 months at least.
Comment #34
Fabianx CreditAttribution: Fabianx commented#33: The only thing that needs work here is that the APC Class Loader is not working.
That is my only complaint of this patch.
I am not sure why the ApcClassLoader Decorator is not implementing addPrefixes, but the normal ClassLoader is, because I think we are doing this right.
Lets get a new patch in and unblock the update.
I am happy to test.
Comment #35
RobLoach+1.... As many have said, Premature optimization is the root of all evil.... But we desperately need the Symfony and Composer updates in as soon as possible. I'm a fan of whatever will get those in sooner rather than later.
Comment #36
catchI don't think getPrefixes() is a public method on every PSR-0 classloader. If it's not, could we look at removing the dependency on whichever classloader we happen to be using from the plugin discovery?
Comment #37
RobLoachIf APC was selected to be used, but was not available, the above patch would fail. This one fixes that. Is that
function_exists()
check really needed? I'd assume if they're asking to use APC, then they know it's there.Correct... This is not a limitation of PSR-0, however. PSR-0 is simply an ideology, not an interface. Most systems wouldn't get a list of namespaces that the classloader has registered. You're right that we should remove that dependency. Let's do that here: #1836008: Remove drupal_classloader() use in Drupal\Core\AnnotatedClassDiscovery
Comment #38
Fabianx CreditAttribution: Fabianx commentedPlease disregard and see below.
===================
#37: Not necessary.
Unnecessary, because APCClassLoader does:
BUT the real problem is:
This needs to happen, before the wrapping, because APCClassLoader does not implement addPrefixes method.
See: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Cla...
And this method also does not exist.
The APCClassLoader is pretty basic.
Can we add our own APCClassLoader that acts like a real decorator / proxy and implements the same functions as the ClassLoader?
============================================
Ah, I got it now :-).
We are just registering the APCClassLoader, but using the normal class loader still for getPrefixes, etc.
Makes sense :-).
Maybe use try / catch to make use of Exceptions?
Comment #39
sunTest failures seem to be irrelevant.
Regardless of that, slightly revamped this patch, and also filed https://github.com/symfony/symfony/pull/5950
Comment #41
sunThe good news: @fabpot merged https://github.com/symfony/symfony/pull/5950 already. :)
The bad news: This patch causes a 300% slowdown in performance. A single web test that normally takes ~20 secs takes ~60 seconds.
I already checked whether the most current ClassLoader in Symfony contains any noteworthy changes compared to the current in core, but there are none.
The only thing that stands out in the callgrind is an exceptional amount of time being spent in annotation parsing. See attached.
It's possible that this is caused by #1836008: Remove drupal_classloader() use in Drupal\Core\AnnotatedClassDiscovery and we might need to resolve that first.
Comment #42
catchSince we blindly use any namespace registered to the classloader, I have a feeling we're scanning everything in vendor/ for plugins. Haven't verified this but it came up when EclipseGc asked me about this issue on irc yesterday.
Comment #43
RobLoach#39: drupal8.classloader.39.patch queued for re-testing.
Comment #45
RobLoachComment #47
Crell CreditAttribution: Crell commented#45: 1658720.patch queued for re-testing.
Comment #49
RobLoachComment #51
RobLoachAnyone know why this fails? We might need the Bootstrap's Bootstrap fix...
Comment #52
Fabianx CreditAttribution: Fabianx commentedComment #54
Wim LeersFrom #1762204-15: Introduce Assetic compatibility layer for core's internal handling of assets.
Comment #55
RobLoachThis patch needs an update. Sun's ClassLoader decorator updates have made it into Symfony now too. Hopefully Symfony has been updated in Core by now as well.
Comment #56
dawehnerRerolled against all the recent changes. I tested a drupal installation and it worked pretty fine.
The original patch contained some "random" changes in some of the composer files, so it seems to be that we should update to recent versions.
Comment #58
sunFor everyone following at home:
1) "Segmentation fault". *ugh*
2) Still a massive slow-down in Annotation parser code/infrastructure. *doubleugh*
3) Fatals on registerPrefixes() and duplicate PrimitiveTypeConstraint class names are most probably real. *tripleugh* ;)
Comment #59
RobLoachaddPrefixes().
Comment #60
RobLoachUploading the correct patch would help.
Comment #61
sunCan you clarify whether this change is auto-generated?
Whatever the technical lookup facility/method is, the vast majority of class file name lookups will be considerably faster when checking for Drupal\Component + Drupal\Core first.
Comment #62
RobLoachEverything in core/vendor/composer is auto-generated by Composer.
Comment #64
RobLoachInstead of
It should be:
Comment #65
dawehnerLet's see whether this last addition should fix it.
Comment #66
chx CreditAttribution: chx commentedmmmmm.
Comment #67
dawehnerThanks chx!
Comment #69
chx CreditAttribution: chx commentedYou need to manually load the Drupal\Component\Utility\Settings class. There is no way to avoid that: the classloader depends on it... but that's OK it's very very small.
Edit: sorry for not realizing this earlier, I wasn't awake properly when doing the previous review as it shows in the highly intelligent remark I made there about the code :)
Comment #70
RobLoachGood catch, chx.
Comment #71
RobLoachComment #72
chx CreditAttribution: chx commentedPutting the require_once into settings(), when settings() might be often called is needlessly slow. And when looking, I found Settings is already manually loaded in
drupal_settings_initialize
. Just do$class_loader = settings()->get('class_loader', 'default');
.Comment #74
dawehner#72: 1658720_70.patch queued for re-testing.
Comment #76
dawehnerJust another rerole, though we still struggle with #1658720-41: Use ClassLoader instead of UniversalClassLoader
I try hard to find the real reason, but I didn't succeeded yet.
Comment #78
jthorson CreditAttribution: jthorson commentedJust me. (Kicking it off the bot so we can test the 5.3.10 upgrade. Has been re-queued on a different bot.)
Comment #80
RobLoachThere are still some follow ups from #1836008: Remove drupal_classloader() use in Drupal\Core\AnnotatedClassDiscovery, so I didn't touch AnnotatedClassDiscovery, but here's an updated patch.
Comment #82
RobLoachSplitting the tests into their own respective test functions fixes the issue.
Comment #83
dawehnerJust fixing two small nitpicks. This looks really good as it is.
Comment #84
RobLoachThanks!
Comment #85
sunLooks great to me. Thanks all!
Comment #86
Fabianx CreditAttribution: Fabianx commentedI guess this should be 'drivers' instead of $baseDir (=== "core")? At least it looks not equivalent to what was changed.
Comment #87
RobLoachGood catch, Fabian. In order to match what was there previously, it should be
"../drivers/lib/"
.There's a follow up at #1805316: Move composer.json to the project root which would allow us to avoid transversing up a directory.
Comment #88
dawehnerBack to RTBC
Comment #89
RobLoachThis patch needs an update... #87: 1658720.patch queued for re-testing.
Comment #91
RobLoachComment #93
RobLoachConfirmed working on APC.
Comment #94
dawehnerThis changes look fine.
Comment #94.0
RobLoachUpdated summary
Comment #95
webchickSeems like catch has been all over this one, so assigning to him.
Comment #96
RobLoach#93: 1658720.patch queued for re-testing.
Comment #97
RobLoach#93: 1658720.patch queued for re-testing.
Comment #98
Crell CreditAttribution: Crell commentedGuaranteed to need reroll after #1805316: Move composer.json to the project root
Comment #99
webchickAlso, it doesn't look like we ever got fresh benchmarks after we figured out this degraded performance by 300%. Let's do that before re-assigning to catch.
Comment #100
dawehnerComment number 100 and a reroll :)
Comment #101
RobLoachNow that composer.json is in the root, it should probably be:
I'll be able to re-roll tomorrow if one isn't up by then! :-)
Comment #102
dawehnerOh right, I'm wondering whether we can and should the composer file.
Comment #103
RobLoachRe-roll with a couple other new instances.
Comment #104
dawehnerThese changes look good.
Comment #105
webchickFresh benchmarks?
Comment #106
RobLoach#103: 1658720.patch queued for re-testing.
Comment #107
RobLoachReports from xdebug in percentage cost...
UniversalClassLoader (current)
ClassLoader (#103)
As I've said before, although the benchmarks do show improvement, the primary benefit of this is not performance. The primary reason for this is that UniversalClassLoader requires you to register
This\Type\Of\Class
(how we namespace our classes) andThis_Type_Of_Class
(PEAR-like) differently (PEAR-like is used in Twig). ClassLoader directly supports PSR-0, which covers both methodologies.Comment #108
RobLoachThis issue isn't about performance, it's about class loading sanity.
Comment #109
dawehnerSo back to RTBC.
Comment #110
webchickYeah, I understand this issue isn't about performance, but earlier patches showed a 300% slowdown in performance, which is totally unacceptable.
Re-assigning to catch for sign-off, thanks!
Comment #111
catchThis looks much better now. My original objection here was to ditching the APC class loader, I'm not yet convinced that a generated classmap is going to be better than APC for Drupal sites, since we tend to have a very high proportion of un-used classes which I doubt most Symfony projects do, at least not without some customization. This doesn't do that any more though and the diff looks great so committed/pushed to 8.x.
It'd be good if changing the classloader didn't require such a large diff, does there need to be a PSR for the classloaders themselves :(
Comment #112
RobLoachNot sure if this would be viable since there are so many different ways of loading classes. Definitely worth considering, however. Psr\Log has proved helpful, Psr\Loader\LoaderInterface could be deemed useful as well.
Comment #113.0
(not verified) CreditAttribution: commentedUpdated issue summary.