Problem/Motivation
The menu tree must be loaded without loading the objects, menu items are linking to.
On every page request, the menu tree is rebuilt. For every Entity (node, term, product, ...) with a menu link, the Entity itself is loaded, too. This is done too often, giving performance problems (memory problems, page load time >> 1sec) or too few, giving rendering-problems (in case a menu is shown a secon time on a page).
We do need some properties for every Entity in the menu:
- its Access must be checked, to see if the Entity is to be included in the menu.
- its title is used for the menu link.
But we do NOT need to load the object (in most cases).
This issue proposes to move the loading of the object to the moment the entity is really needed.
Proposed resolution
When building the menu tree using _menu_link_translate(), function _menu_load_objects() is called too often or too few. Attached patch tries to resolve the problem, not only the symptom: _menu_load_objects() should be called just-in-time.
The attached patch gives these results on a page with admin_menu, a megamenu and a menu_block, and a menu structure that contains a taxonomy_menu of 2 vocabularies (of 63 and 13 tems):
1. original code of menu.inc:
Executed 373 queries in 258 ms. Page execution time was 1973 ms. Memory used at: devel_boot()=1.5 MB, devel_shutdown()=46.09 MB, PHP peak=47.75 MB.
Executed 374 queries in 253 ms. Page execution time was 1763 ms. Memory used at: devel_boot()=1.5 MB, devel_shutdown()=46.09 MB, PHP peak=47.75 MB
Executed 373 queries in 244 ms. Page execution time was 1752 ms. Memory used at: devel_boot()=1.5 MB, devel_shutdown()=46.09 MB, PHP peak=47.75 MB.
2. patched code of menu.inc:
Executed 248 queries in 229 ms. Page execution time was 1705 ms. Memory used at: devel_boot()=1.5 MB, devel_shutdown()=45.61 MB, PHP peak=47.5 MB.
Executed 248 queries in 219 ms. Page execution time was 1704 ms. Memory used at: devel_boot()=1.5 MB, devel_shutdown()=45.61 MB, PHP peak=47.5 MB.
This is how it works: The callstack in question is doing something like this:
menu_tree_all_data($menu_name)
->menu_tree_check_access($tree, $node_links)
-->_menu_tree_check_access(&$tree)
--->_menu_link_translate($item)
----> _menu_check_access($item, $map);
-----> menu_unserialize()
----> _menu_item_localize($item, $map, TRUE);
-----> menu_unserialize()
Proposed patch moves _menu_load_objects() from _menu_check_access($item, $map) to menu_unserialize().
This way, the object is loaded only when it is needed.
Remaining tasks
- In the core code, Nodes are loaded all-at-once in a special request, all other Entities 1-by-1. This seems out-dated, with Entities becoming ubiquitous.
- menu_unserialize() is really an internal function to menu.inc, so should be renamed to _menu_unserialize().
User interface changes
None.
API changes
Some internal functions have a changed interface. The public API is not changed.
Related Issues
The following issues all have different symptoms and solutions for the same problem.
#753064: _menu_link_translate() might avoid calling _menu_load_objects()
If you have menu items that refer to views (or any other expensive load_functions for that matter), this patch will dramatically increase your sites performance.
#1697570: _menu_load_objects() is not always called when building menu trees
...I wanted to display the main menu two times, one time as a dropdown menu like described above, and one time in the footer. Everything works fine with that, output is just like expected. But if there are two blocks, the following type of error occurs for every menu-item that links to a node: ....
#1973920: Memory/Performance problems, when building a menu with a large taxonomy_menu
I have taxonomy_menu enabled... The whole vocabulary is read term-by-term by function _menu_load_objects(). In most cases, the menu can be generated without loading every term.
#1905144: High load time for admin_menu upon user login/menu refresh
I noticed that admin_menu is rebuilding the menu every time user is logging in. Login page loads on average in 30 seconds, which is way too long.