On a vanilla D7 install, _menu_navigation_links_rebuild() causes the following database queries:
menu_link_save() - 274 calls to db_query()
_menu_link_find_parent() - 264 calls to db_query()
_menu_navigation_links_rebuild - 274 calls to SelectQuery::execute
menu_link_children_relative_depth - 24 calls to SelectQuery::execute
This is all without actually saving the link which is already optimized for, these are all called before we're able to make that decision.
The following patch adds two optional arguments to menu_save_item(), both intended for use only from _menu_navigation_links_rebuild().
The first is the $existing_item that we already load in _menu_navigation_links_rebuild(), which is almost immediately queried again with more fields in menu_save_item().
The second is the overall $menu_links array - since parents are built first, we can look through that for current item's parent ID instead of querying it from the database.
This reduces total queries (to the front page with a forced rebuild) from 983 to 447. This is the most conservative improvement we'll get given real sites will have more modules and links.
It also reduces memory usage for that page from 23mb to 12mb on my machine - most of this through not having to invoke dbntg/PDO so much. Page execution time was reduced from 1700ms seconds to 900ms.
Patch and xhprof screenshots attached (edit: 26.png is 'after', 27.png is 'before').
Comment | File | Size | Author |
---|---|---|---|
#57 | menu_navigation_links_rebuild.patch | 4.32 KB | catch |
#53 | menu_navigation_links_rebuild.patch | 4.32 KB | catch |
#52 | menu_navigation_links_1010480.patch | 4.43 KB | catch |
#50 | menu_navigation_links_1010480.patch | 4.19 KB | catch |
#22 | menu_navigation_links_1010480.patch | 4.34 KB | catch |
Comments
Comment #2
catchThis fixed some of the test failures locally, the other tests are taking so long to run on my machine I've run out of patience.
Comment #3
catchFixing one small bug with $existing_item['options'];
Comment #7
catchMinus the syntax error.
Comment #8
pwolanin CreditAttribution: pwolanin commentedI don't see that the new 3rd param to menu_link_save() is used any place.
Comment #9
catchThat's because I'm an idiot and uploaded the wrong patch. This should be the right patch but it hasn't had a test run yet.
Comment #10
catchFixes one notice when items in the $menu_links array don't already have an mlid.
Updated xhprof output, I think the previous one was a cache miss in APC or something, since I'm now seeing a 1.2mb memory saving, not 12mb as previously.
Comment #13
Damien Tournoud CreditAttribution: Damien Tournoud commentedvs:
The only items in $menu_links that can possibly have a mlid are those that have already been passed through menu_link_save(). As a consequence, it would make more sense to me to build a separate array keyed by mlid and avoid the expensive scan.
Comment #14
catchAnd without the crud. Sorry for rapid fire patch uploads.
Comment #15
catchI was thinking about the separate array but didn't want to risk bloating memory with a large number of links. What about just
if (!isset($item['mlid'])) { break; }
in that foreach?Menu tests pass locally for me with the latest patch, don't like the look of '0 passes' though.
Comment #16
Damien Tournoud CreditAttribution: Damien Tournoud commented@catch: well you can progressively prune the initial array if you like.
Comment #17
catchHow could we prune it though? Multiple links can share the same parent, so we can't just remove items as they're found. Or did you mean something else?
Comment #18
Damien Tournoud CreditAttribution: Damien Tournoud commentedI meant pruning the links from $menu_links when we have processed them. Also, it might make sense to *also* keep the customized links:
... as this doesn't currently update $item with its mlid if an existing item exists and is customized.
Comment #19
catchOh I see what you mean now, prune the old array while building the new one, OK. I'm out of time for tonight but will have a look at that tomorrow. Vanilla install doesn't have any customized links so we may well get an improvement from keeping those two with actual data sets.
Comment #20
carlos8f CreditAttribution: carlos8f commentedsubscribing
Comment #21
catchBuilding that extra array array cuts 46ms from _menu_link_find_parent() on my machine. Slight improvement, definitely no regression in memory usage.
Before: 47,208 / 6%
After: 1,474 / 0.2%
Query count is the same (actually 2 less but didn't look to see where those are from so might be something else happening).
Comment #22
catchPeople in irc didn't like the concise version of $existing item, so reverted that change. Code style rather than functional change.
Comment #23
moshe weitzman CreditAttribution: moshe weitzman commentedLooks reasonable to me. We're gonna need donquixote or chx or pwolanin here I think.
Comment #24
pwolanin CreditAttribution: pwolanin commentedI'll take a look at it later today - maybe see you tonight Moshe.
Comment #25
pwolanin CreditAttribution: pwolanin commented@catch - query count is the same as the prior patch, or the same as without the patch? It seems like the patch must result in a significant reduction in queries compared to unpatched core.
Comment #26
catch@pwolanin - same as the prior patch.
Comment #27
pwolanin CreditAttribution: pwolanin commentedThe logic of the code looks good to me.
Comment #28
catch#22: menu_navigation_links_1010480.patch queued for re-testing.
Comment #29
Andrew Answer CreditAttribution: Andrew Answer commentedCan you backport this patch to 6.20 here http://drupal.org/node/593682 ?
Comment #30
mikeytown2 CreditAttribution: mikeytown2 commentedsubscribe
Comment #31
donquixote CreditAttribution: donquixote commented@Moshe (#23):
This is buried too deep in my head atm to review.
Still, I definitely want to be subscribed.
Comment #32
donquixote CreditAttribution: donquixote commentedLooking at it:
I initially had the idea that we would totally bypass menu_link_save(), and have some kind of chunked bulk update instead.
That said, if this rather simple patch can significantly speed things up (which I have not tested yet), then I'm all for it.
I wonder if it can be backported to D6.
[side note]
For my own sites I was already thinking about skipping this part of menu rebuild (which would be a core hack, yes). I don't need the navigation menu, and I have my own solution for admin menu. I wonder if _menu_navigation_links_rebuild() is necessary for anything else? All other menus' items are considered "customized", so _menu_navigation_links_rebuild() leaves them untouched - afaik.
[/side note]
Comment #33
catchI haven't looked a the possibility of backporting this to D6, but that ought to be doable (although IME performance issues are unlikely to be committed to D6 at this point, but maintaining patches in the queue and submitting them to pressflow is worthwhile).
Marked #593682: Optimize _menu_navigation_links_rebuild() as duplicate - was the older issue, but did not have a Drupal 7 patch.
Comment #34
Andrew Answer CreditAttribution: Andrew Answer commented@catch: what do you suggest? No way to speed up admin UI on thousands D6 existing installations? Bad. If donquixote make something viable my big thanks to him!
Comment #35
catch@Andrew, if this patch gets into D7, then it's worth trying to backport the patch to D6 - but Drupal 6 has no automated testing, so issues like this which only help performance but nothing else are less likely to get in than straight bugfixes - every patch committed risks unintentionally breaking something else. However that doesn't stop you applying the patch to your own D6 install, or someone submitting it to Pressflow to try to get it incorporated there. It might end up going into Drupal 6, that's not up to me, but those are the options if it doesn't.
Comment #36
effulgentsia CreditAttribution: effulgentsia commentedsubscribe
Comment #37
Andrew Answer CreditAttribution: Andrew Answer commented@catch, the best way I see to help developing is to implement automated testing system for D6 too :) - at least I know what SimpleTest module already backported to D6 in Pressflow. Probably testing architecture much complex than I know, but anyway this is a very good thing for all D6 users, much better than immediate upgrade to D7. I think, users will send D6 patches at least a year more.
You also do not reject patches only because you can't test it automatically. Yes, we can have side effects with these patches, but we can fix it manually... and fix again... and release finally, improving engine at the end. Performance issues ones of important thing for Drupal any version I think.
I create bug in Pressflow's Launchpad for that, but looks like they pre-moderate bugs and decline it by undefined cause. Or I misunderstand his UI :(
Comment #38
carlos8f CreditAttribution: carlos8f commentedGetting stuff backported to D6 at this stage, if it's not a security bug, is probably impossible :) I haven't had any luck with #235673: Changes to block caching mode not caught. So I just end up applying a handful of patches to my production D6 sites manually.
Comment #39
effulgentsia CreditAttribution: effulgentsia commentedI disagree with this suggestion. The process of making improvements, discovering side effects, fixing them, and thereby improving Drupal, is what the version of Drupal that is in development is for. Which until recently was Drupal 7, and will soon be Drupal 8. A released and widely used version (e.g., 6) of Drupal core is frozen: minor updates (e.g., 6.20 to 6.21) are for bug fixes that are either critical (e.g., security) or without risk of side effects (e.g., fix a typo).
There may be a lot of people who will continue using Drupal 6 for a while, and they may want performance improvements, but they can get that by patching their installation, or using a distribution like Pressflow.
Creating a backported patch of #22 is worth doing, as is using the issue queue to collaborate, review, and share results about that patch. Anyone is welcome to create such a patch and open a new issue for it, or wait until this patch is committed to D7, and then continue using this issue for the D6 backport. But I don't think it makes sense to expect a D6 backport of this to be committed to CVS, unless it can be sufficiently shown to have 0 side effects. The D6 maintainers, Dries and Gabor, make the final call on what constitutes "sufficient", and they're both very reasonable in taking community input into account.
Every once in a while, people bring up the desire to have more improvements happening in minor updates instead of all being pushed into the next major one. With Drupal 7 released and including good automated test coverage, it's a good time to have that discussion again, with respect to how frozen we want D7 to be now that it's released. But that discussion won't change what we do for D6, and please, let's not have that discussion here. Here's one place: #586146: [policy, no patch] Decide if and how to implement semantic versioning for Drupal 8.x, though if anyone knows of a better one, please add a comment to that issue linking to it.
BTW: I support this patch going into D7, because although it too is an already released version, we've always been a little more liberal in what we allow into the first few minor updates of a new major version.
Comment #40
casey CreditAttribution: casey commentedCan't we add an _menu_link_save(&$item, $existing_item = array(), $parent_candidates = array())? So we don't alter the API?
Comment #41
catchSince it's only adding extra arguments, not changing existing ones, I didn't see any reason to do that. If it's a choice between adding duplicate code and not getting it in at all, then I'd got for the new private function though.
Comment #42
casey CreditAttribution: casey commentedmenu_link_save() could call _menu_link_save(), no need for duplication.
Comment #43
casey CreditAttribution: casey commentedAnother optimization: #1034732: API additions: menu_link_load_multiple() and menu_link_delete_multiple(). that one could use the $existing_item parameter too, in _menu_link_delete_multiple().
Comment #44
catch#22: menu_navigation_links_1010480.patch queued for re-testing.
Comment #45
Renee S CreditAttribution: Renee S commentedsubscribe (I would definitely love to see a D6 version of this, even if it's something that doesn't make it in. Still useful, and as mentioned above, it could be Pressflowed...)
Comment #46
catchComment #47
catchComment #48
catch#22: menu_navigation_links_1010480.patch queued for re-testing.
Comment #50
catchRe-rolled, this had been RTBC for nearly 5 months and no longer applied.
Comment #51
catch#50: menu_navigation_links_1010480.patch queued for re-testing.
(sending for retest but I am coming 'round to casey's _menu_link_save() idea so will probably re-roll with that)
Comment #52
catchAnd with _menu_link_save().
Comment #53
catchchx reviewed in irc, no functional change but simplified a couple of bits.
Comment #54
donquixote CreditAttribution: donquixote commentedI don't want to stop this patch, but I'd like to point out two related things, that might make an even faster nav links rebuild possible.
#512962: Optimize menu_router_build() / _menu_router_save()
The latest patch will make it possible to let the nav links rebuild know which of the router items have changed.
Btw, this thing needs attention / feedback.
#1170278: Introduce hook_menu_router_update()
This would be the mechanism to let nav links rebuild know about the changes.
Depends on the other issue.
Doing this does not mean we no longer want this patch. There might be situations where no router diff is available, or the full router table has changed.
Comment #55
catch#53: menu_navigation_links_rebuild.patch queued for re-testing.
Comment #56
chx CreditAttribution: chx commentedAlmost RTBC but
that's a whitespace error before unset.
Comment #57
catchThanks chx, fixed the space.
Comment #58
chx CreditAttribution: chx commentedWell then
Comment #59
webchickThis seems like a great little backwards-compatible performance improvement. Really sorry, I have no idea how it stayed off the radar so long. :(
Committed and pushed to 8.x and 7.x. Thanks!
Comment #60
catchWhile I doubt it'll get in, I'll probably do a backport for D6 of this if it's straightforward at some point.
Comment #61
deltahex CreditAttribution: deltahex commentedComment #62
hefox CreditAttribution: hefox commentedConfused about the status of this patch; which (if any) is the d6 version of the patch to review?
Comment #63
donquixote CreditAttribution: donquixote commentedSome feedback:
I did some xdebug on a D7 test site, with a recent version of D7, and this is still undesirably slow.
Comment #64
donquixote CreditAttribution: donquixote commentedRe #63
Probably the current solution is still the best that we can get without breaking anything.
------
I am currently working on a rewrite of _menu_navigation_links_rebuild(), which promises to be quite fast. That is, it will really make a difference.
Unfortunately, it might be a bit fragile when backported to D7, due to hook_menu_link_alter().
I have the feeling this is not a very well-designed hook.
The way out:
This will allow contrib to kick the smelly core implementation of _menu_navigation_links_rebuild() and replace it with something else, without being overly concerned about compatibility.
Diff-based rebuild of menu_router, so that the diff can be available to _menu_navigation_links_rebuild().
I originally thought this should be done before we introduce hook_menu_router_update(), but actually it doesn't matter. We simply extend the signature of hook_menu_router_update(), adding additional arguments for the diff.
Well, this might be the case. But we still want these improvements for D7.
So we should get them into D8 for backport to D7, before Symfony makes all of this unportable.
Comment #65
donquixote CreditAttribution: donquixote commentedI am working on a rewrite in a separate issue.
#1830274: Support a parent_path setting in hook_menu(). / hook_menu_link_alter() should not touch the plid.
Speed improvement is between 2x and 3x.
As mentioned, what makes this more difficult is the
drupal_alter('menu_link')
.This hook itself is ok, but the pain comes when modules alter the plid of the item.
If this stops to happen, we can have a more predictable logic:
- non-customized items always get their parent link id only from hook_menu().
- customized items either keep the parent link they had before, or they move up.
hook_menu_link_alter() should not have a say in this.
Comment #66
kenorb CreditAttribution: kenorb commentedNot much activity here, and Drupal 6 is depreciated.
Fixed in 8.x and 7.x according to #59
Comment #67
kenorb CreditAttribution: kenorb commentedComment #68
David_Rothstein CreditAttribution: David_Rothstein commentedPutting this back to the last fixed version, in that case.
Comment #70
yonailo CreditAttribution: yonailo as a volunteer commented