Problem/Motivation
LanguageManager::getDefinedLanguageTypesInfo() calls t() 52 times on an anonymous request to node/1 with the standard profile and warm caches.
This is to translate... the human readable names of the language types, of which we have two.
Proposed resolution
Two options:
Use TranslationWrapper so that the strings are only translated when used.
Remaining tasks
-
So looks like the comment needs fixing because its not true. We can choose to forego implementing the hook in language and just hardcode calling the parent in ConfigurableLanguageManager() for collecting the defaults and call the hooks afterward. The content language title would still need to be wrapped in TranslationWrapper.
User interface changes
API changes
Beta phase evaluation
Issue category | Bug because translating strings 50 times per request that are never rendered is unnecessary overhead |
---|---|
Issue priority | Major, as explained in #2497275-14: ~50 calls to t() for two strings in LanguageManager() on every request |
Comment | File | Size | Author |
---|---|---|---|
#36 | interdiff.txt | 646 bytes | borisson_ |
#36 | 50_calls_to_t_for-2497275-36.patch | 6.89 KB | borisson_ |
#32 | 50_calls_to_t_for-2497275-32.patch | 6.73 KB | borisson_ |
#32 | interdiff.txt | 2.42 KB | borisson_ |
#29 | 50_calls_to_t_for-2497275-29.patch | 7.02 KB | borisson_ |
Comments
Comment #1
Wim LeersComment #2
Gábor HojtsyI don't think we can move it to class variables, unless we initialize them in the constructor? That sounds fine with me. For later translation, we can use a TranslationWrapper, but not sure that would come out more performant?
Comment #3
catchYes we'd have to do that.
Combination of the two would mean we only create one class instance (per-string) and probably never translate most requests.
Combined with #2497259: system_region_list() unnecessarily translates region names we start getting down to a very low number of string translation on most requests - this opens up the possibility (at least with lots of render caching/SmartCache) to not have to initialize the string translation services at all on warm caches.
Comment #4
alexpottHere's a patch.
Comment #5
Wim LeersLooks great, assigning to Gábor for final review.
Comment #6
Wim LeersComment #7
Gábor HojtsyWhy not change the names for content and URL and the description for URL?
Wait, URL does not have a name or description here. But as per the comment you modify it should because language_language_type_info() has those.
Comment #8
alexpottHmmm... but we have code that checks the result of this and looks for name and does different things... /me is confused.
Comment #9
Gábor HojtsyI think the only place was in views and we don't do that anymore but tie to locked there too.
Comment #10
alexpott@Gábor Hojtsy afiacs we have 3 places in core where we are checking for the name.
in Drupal\views\Plugin\views\PluginBase
Comment #11
Gábor HojtsyOh, so ConfigurableLanguageManager::getLanguageTypes() only returns configurable types, which both the block code is based on and the views plugin base. The query replacements are just query replacements, so having non-configurable options there does not cause problems. So looks like LanguageManager::getDefinedLanguageTypesInfo() should not have URL label and description indeed, because LanguageManager::getLanguageTypes() returns all types, not just the configurable ones. (or we should make LanguageManager::getLanguageTypes() only return the configurable ones).
So looks like the comment needs fixing because its not true. We can choose to forego implementing the hook in language and just hardcode calling the parent in ConfigurableLanguageManager() for collecting the defaults and call the hooks afterward. The content language title would still need to be wrapped in TranslationWrapper.
Comment #12
dawehnerPing, so #11 makes totally sense. Do you plan to work on it? I think these two steps could be done pretty quickly.
Comment #13
Gábor HojtsyNot planning to work on it. I think we can skip implementing the hook in language module too and call the parent, that would make us not required to do the same changes at two places and keep them consistent, which is a PITA.
Comment #14
dawehnerSo this issue itself saves 2,8ms for me which is ~2,5% of the entire request for the default frontpage anonymous, without page cache.
On the slower computer of catch we might get to > 5ms easily, which 5% of the default frontpage.
IMHO this is at least major then.
When catch and myself talked a bit more we realized what else could be skipped:
so while this issue itself does NOT save 10ms, it will enable further optimizations.
Comment #15
dawehnerI pushed too much because of stupid rules.
Comment #16
catchI think this can be done without any API changes, so bumping back down to major.
But agreed that this gets us towards two goals:
1. Even on multilingual sites, not initializing the translation system on render cache hits.
2. Reducing the number of state calls - that's currently ~5-10 database queries per page, direct to the database with no cache wrapper.
Comment #17
dawehnerOpened a follow up: #2537042: [meta] Make t() more lazy and added some tasks in the issue summary.
Comment #18
dawehnerThat wasn't me, sorry
Comment #19
catchAnother reason this is important.
Assuming we get smart cache in, then nearly everything on the page will be render cached.
When you view non-English sites, translation of strings has a real cost, but render cached strings don't need to be translated each time.
Places like this where we translate something that doesn't get rendered, completely undermine this situation - because we always have a dozen or so strings translated every request, even though they never get rendered anywhere - can't be fixed by render caching.
So more optimized our caching gets, the higher impact issues like this get - since we're taking it out of the baseline time to render any full page.
It also helps at the other end of the scale - where you have a request that doesn't render anything, but needs to check language types for whatever reason, then with a cold cache, this can allow the site to completely avoid looking up translations for that request - allowing often-blocking rebuilds to return faster. i.e. if you check language types in router or theme registry rebuild it takes database queries out of that process, and saves cache sets at the end of the request.
Comment #20
dawehnerblub.
Comment #21
borisson_Comment #22
borisson_Comment #23
borisson_Removed the hook from language module, and get the default information from languageManager, as suggested in #13. Also wrapped the content language title in TranslationWrapper as suggested in #11.
I didn't run all the tests locally but
ConfigLanguageOverrideWebTest
andConfigLanguageOverrideTest
do work. So getting the defaults without the hook seems to work.Let's see what the rest of the testsuite thinks.
Comment #25
borisson_Looks like all of the fails are because when the language module isn't enabled, it can't load the METHOD_ID constant on LanguageNegotiationUI / LanguageNegotiationUrl / LanguageNegotiationUrlFallback.
I could move those constants to the LanguageManager, but that doesn't feel like a correct solution.
Comment #26
borisson_After discussing this in IRC with dawehner I've set the fixed property from the language module so not use the constants in the core class.
Comment #27
borisson_Comment #28
tstoecklerAssignment and returning in a single line is fairly standard in core. Can we put the return on a new line after the rest?
Also, it seems since we're already storing the variable it would make sense to early return here as well, not just in the child implementation?
I like that we're no longer duplicating information. Yay!
I still think we should implement the info hook (like it is now), not the alter hook. Because
ModuleHandler::invokeAll()
does a recursive merge this works perfectly and is more in-line with our "build-then-alter" pattern, because here we just adding information not altering existing information.Seems this could move inside of the if now?
Comment #29
borisson_Fixed 1 and 3 out of #28. Not sure about 2.
I think this is what @Gábor Hojtsy meant in #11/ #13.
Comment #30
tstoecklerAhh yes, sorry #28.2 in fact will not work just like this, because here we are manually merging the arrays with
+
ourselves, so that would have to replaced with a deep merge for my proposal to work. (I still think that makes sense because it is more inline withModuleHandlerInterface::invokeAll()
, but it will not just work to use the info hook instead of the alter)Comment #31
Gábor HojtsyNitpick: goes over 80 columns
Not true anymore (and the function name was incorrect anyway).
Yay :)
Should not add that newline IMHO.
Comment does not add value.
Comment #32
borisson_Fixes comments from #31
Comment #33
Gábor HojtsyLooks good, makes sense, except:
Does not explain the array structure so it needs to use somehow also inheritdoc, no?
Nice!
Are we sure we fixed the performance issue here?
Comment #34
Wim LeersComment #35
dawehnerAnonymous frontpage, no page cache. This certainly saves a bit.
When I look at an invidual providing you can't see any translate() call anymore in there.
Comment #36
borisson_Regarding #33, we're sure this fixes the performance issue, @dawehner profiled this in #35.
Also changed the comment of 33.1 so it describes what the return array looks like. The entire wording that {@inheritdoc} would give us isn't correct in this case.
Comment #37
dawehnerI think this is ready to fly now
We need to document this new hook.
Comment #38
dawehnerI talk bullshit.
Comment #39
catchComment #41
catchUpdated the issue summary a bit.
Patch looks great now, and while I opened the issue, I didn't actually write any patches, so I think I'm OK to commit.
Committed/pushed to 8.0.x, thanks!
Comment #43
Gábor HojtsySuperb, thanks! Great cleanup also!