Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
Function calls require_once so using drupal_static() and clearing this static is pointless.
On cache flush:
Before:
Total Self: 94ms
Total Cumulative: 163ms
Calls: 2,982
After:
Total Self: 49ms
Total Cumulative: 72ms
Calls: 2,982
Total savings ~90ms.
Proposed resolution
Add static cache to module_load_include()
Remaining tasks
Task | Novice task? | Contributor instructions | Complete? |
---|---|---|---|
Profiling | Instructions | ||
Update the issue summary noting if allowed during the beta | Instructions | ||
Review patch to ensure that it fixes the issue, stays within scope, is properly documented, and follows coding standards | Instructions |
Comment | File | Size | Author |
---|---|---|---|
#106 | add_static_cache_to-1443308-106.patch | 843 bytes | cilefen |
#100 | Screen Shot 2015-12-17 at 12.01.52.png | 171.18 KB | alexpott |
#100 | 1443308-100.patch | 1.91 KB | alexpott |
#100 | 91-100-interdiff.txt | 420 bytes | alexpott |
#94 | XHProf__Hierarchical_Profiler_Report.png | 216.25 KB | joelpittet |
Comments
Comment #1
mikeytown2 CreditAttribution: mikeytown2 commentedComment #2
mikeytown2 CreditAttribution: mikeytown2 commentedNon Cache Flush:
Before:
Total Self: 21ms
Total Cumulative: 27ms
Calls: 312
After:
Total Self: 15ms
Total Cumulative: 16ms
Calls: 311
total savings ~10ms
Comment #3
mgifford#1: drupal-1443308-1-add-static-cache-to-module_load_include.patch queued for re-testing.
Comment #4
PlayfulWolf CreditAttribution: PlayfulWolf commentedNo testing results for a month??
Comment #5
mikeytown2 CreditAttribution: mikeytown2 commentedpatch is still green in #1 so it passed all tests.
Comment #6
PlayfulWolf CreditAttribution: PlayfulWolf commentedSo, there are plans to apply it to next release or you need more testers?
Comment #7
mikeytown2 CreditAttribution: mikeytown2 commentedThis is core so I have no control over if and when this goes in. The first step would be for the status to change from needs review to reviewed & tested by the community. That requires people saying that this patch works. So far I'm the only one who has stated that.
Comment #8
tstoecklerCan you explain what this patch does, I don't get it. Thx.
Comment #9
mikeytown2 CreditAttribution: mikeytown2 commentedGreat question as the patch in #1 was missing an important bit of code. Also looks like on my test environment this doesn't show any sort of improvement as before... So this patch is useful if you have code stored on a slow drive like NFS. We recently switched code off of NFS so we don't see the improvements now. Oh well...
On cache flush:
module_load_include()
Before:
Total Self: 43ms
Total Cumulative: 85ms
Calls: 1,702
After:
Total Self: 45ms
Total Cumulative: 84ms
Calls: 1,995
Total savings ~1ms.
drupal_get_path()
Before:
Total Self: 40ms
Total Cumulative: 55ms
Calls: 3,015
After:
Total Self: 39ms
Total Cumulative: 54ms
Calls: 2,925
Total savings ~1ms.
is_file()
Before:
Calls: 1,712
After:
Calls: 1,612
Total savings ~100 less calls to built in PHP function is_file.
Conclusion: Unless your code is stored on a slow drive, this patch is pointless.
Comment #10
PlayfulWolf CreditAttribution: PlayfulWolf commentedIn general, the share is growing of hosting environments where the VPS/user VMs are on relatively light and inexpensive servers and files are on SAN/NAS/dedicated *raid storage machines, so this patch still makes sense, right?
p.s. I am just curious, because found link to this patch in many discussions, where drupal is running slow because of quantity of files and similar stuff.
Comment #11
tstoecklerWell, if we can prove that this improves performance on some environments without hurting it (noticably) on others, that would be a strong case to get this in. Especially if those environments are shared hosters, which Drupal generally does not perform well for.
I'm pretty sure this applies to Drupal 8 as well, and hence should go there first, but could be backported all the way to Drupal 6. Adding tags accordingly.
Comment #11.0
tstoecklerUpdated issue summary.
Comment #12
mgifford9: drupal-1443308-9-module_load_include-static-cache.patch queued for re-testing.
Comment #15
mikeytown2 CreditAttribution: mikeytown2 commentedUpdating tags to show that the patch in #9 is for D7. Will retest patch as D7 and then once this passes we can move the issue back to D8.
Comment #16
mikeytown2 CreditAttribution: mikeytown2 commented9: drupal-1443308-9-module_load_include-static-cache.patch queued for re-testing.
Comment #17
mikeytown2 CreditAttribution: mikeytown2 commentedPatch passes. Moving issue back to D8
Comment #20
joelpittetRe D7 #9 Commerce Views listing page on a site that has ~375 enabled modules.
I love this module it saves > 1sec load time on cold caches after registry rebuild.
Comment #21
joelpittetDo we need to deal with $name = NULL? xmlsitemap module has an example of this use case.
Comment #22
joelpittetHere's the patch for D8.
Comment #23
joelpittetComment #24
joelpittetComment #25
mikeytown2 CreditAttribution: mikeytown2 commentedRE #21: NULL should be cast to an empty string
http://php.net/types.type-juggling#24809
Comment #26
joelpittet@mikeytown2 I know that much, I guess my question was too generic there, sorry.
I was just wondering if there would be any cache collisions with 'type:module:'?
Comment #27
mikeytown2 CreditAttribution: mikeytown2 commentedFairly certain there would not be a cache collision.
Comment #28
joelpittetI think so too, thought I'd ask. Let's see if we can get this into D8/7/6:)
Comment #29
penyaskitoI would suggest using module if name is empty (from docblock: If omitted, $module is used). This way we hide the implementation detail AND increase the hit rate if we have code that explicits name=module and name=NULL.
Comment #30
joelpittet@penyaskito we have a check for !isset($name), I'd rather not change functionality to
empty()
as it could be disruptive, no?Comment #31
YesCT CreditAttribution: YesCT commentednormal feature request is a red flag.
Needs a beta evaluation. instructions are in the summary.
Comment #32
penyaskito@joelpittet Oops, I miss-looked at it, the key never will look like 'type:module:', so IMHO there is nothing wrong on the last patch.
Comment #33
Fabianx CreditAttribution: Fabianx commentedThanks, YesCT, this is mis-categorized.
Uhm, that is a task, not a feature request.
Depending if you can show that it saves much, it can even be major.
Comment #34
mikeytown2 CreditAttribution: mikeytown2 commented@Fabianx
#20. Mainly helpful if the code is on a slower disk (NFS, Virtual, etc). There is minimal downside to this from what I can see. My original tests were with apc.stat=0 so that can be ruled out as a possible solution.
Comment #35
jhedstromIt would be helpful if somebody could re-run the benchmarks listed in the issue summary on current D8.
With this change, would it also make sense to use
require
instead ofrequire_once
?Comment #36
pendashteh CreditAttribution: pendashteh commentedCorrecting miss-spelled tag.
Comment #37
btopro CreditAttribution: btopro at Pennsylvania State University commentedreroll of #9 against latest
Comment #39
btopro CreditAttribution: btopro at Pennsylvania State University commentedTested before and after cache clear on a pretty complicated site (154 modules, decent caching mechanisms in place, php / mysql 5.5 / opcache). This is running on a Local CentOS 6.5 Vagrant instance.
Prior to patch
4103
After patch
2394
Comment #40
btopro CreditAttribution: btopro at Pennsylvania State University commentedswitching to 7.x temporarily to allow test bot to pick it up correctly
Comment #42
btopro CreditAttribution: btopro at Pennsylvania State University commentedback to 8 since that's where thread was but the patch I rerolled for #37 passed tests
Comment #43
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedUhm, RTBC? Looks pretty straightforward to me.
Comment #44
penyaskito#35 is still unanswered. Could we use
require
instead ofrequire_once
? What is the gain?Comment #45
btopro CreditAttribution: btopro at Pennsylvania State University commentedperformance difference between the two should be nominal on anything running opcache (5ms otherwise maybe, pushing it to suggest that high even)
Comment #48
marcingy CreditAttribution: marcingy at Examiner.com commentedJust looking through this patch and any reason why it does
Instead of
As currently this will attempt to load multi times when a file is missing.
Comment #49
marcingy CreditAttribution: marcingy at Examiner.com commentedComment #50
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedBack to RTBC - Should profile this again with DB in IS.
Comment #53
mgiffordThis has to be for the D7 version.
Comment #55
mikeytown2 CreditAttribution: mikeytown2 commentedShould actually be RTBC
Comment #56
David_Rothstein CreditAttribution: David_Rothstein as a volunteer commentedThis looks interesting, but still needs to go into Drupal 8 first.
Just for fun, it looks like Drupal 8 has two functions that do a similar thing, module_load_include() itself plus ModuleHandler::loadInclude(). Perhaps the static cache would be useful on both.
Comment #57
joseph.olstadOk, too easy.
:P
Comment #58
joseph.olstadI have no idea if this will pass or not, I didn't test it but looks simple enough.
Comment #60
joseph.olstadok, start simple this time.
Comment #61
joseph.olstadComment #62
joseph.olstadOk, patch 60 passed, now try this one just for fun.
*EDIT* I cancelled tests for this patch and then wrote a new patch #64
Comment #64
joseph.olstadI'd do an interdiff, but it's not that big.
basically patch 62 and 56 missing key value on loadInclude, putting that in, should get better test results
Comment #65
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedDon't think we can test this ...
Edit: The patch is RTBC. I don't think there is a way to test an internal static, therefore I am marking as RTBC and also rc target triage so it is considered for the RC phase.
Comment #66
joseph.olstad@Fabianx, I am confused by your comment #65
Comment #67
cilefen CreditAttribution: cilefen commentedWouldn't it make more sense to call this $file_names?
I don't understand how this works. If the static cache is not set, we return the file name without checking if it exists. What would be returned in this case?
Comment #68
cilefen CreditAttribution: cilefen commentedComment #69
jonhattanChanges:
* Use
$files
as the static variable name instead of$file_name
* Fixes #67.2 - the logic implemented in module_load_include() is wrong
* Reorder the code to use only a
return
statementComment #70
cilefen CreditAttribution: cilefen commented@jonhattan Nice!
Comment #71
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedThis is wrong, we should not say a file does not exist, when the drupal_get_path function is not (yet) in scope. We should ignore the static cache in that case.
I think adding it in an else {} block should be fine though as it is unlikely that drupal_get_path result changes within the same request.
However the safer version is to only populate the cache when an item is found.
Comment #72
cilefen CreditAttribution: cilefen commentedIs there a reason we are not using drupal_static()?
Comment #73
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commented#72: Yes, IS states so:
files can only be added not removed during a run, so there is no need to ever clear the static - as the function calls require_once.
Comment #74
cilefen CreditAttribution: cilefen commentedComment #75
catchModuleHandler can use a class property, doesn't need to be an actual static.
Comment #76
cilefen CreditAttribution: cilefen commentedThis is the recommendation in #75. #71 still needs doing.
Comment #77
xjmThanks for tagging this for rc target triage! If you want committers to consider it for inclusion in RC, add a statement to the issue summary of why we need to make this change during RC, including what happens if we do not make the change and what any disruptions from it are. You can add a section
<h3>Why this should be an RC target</h3>
to the summary.Comment #78
cilefen CreditAttribution: cilefen commented@Fabianx Re #71: How do we resolve the path the file reliably without drupal_get_path()?
Comment #79
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commented#78: What I mean is:
We do _never ever_ want to cache the result when drupal_get_path function is not available as that will always be wrong.
Comment #80
cilefen CreditAttribution: cilefen commentedComment #81
cilefen CreditAttribution: cilefen commentedComment #83
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedLooks great now - RTBC - except for nit, still needs profiling and IS update:
superfluous - space
Comment #86
stewsnoozeFixed the tiny whitespace niggle that Fabianx mentioned in #83
Comment #87
cilefen CreditAttribution: cilefen commentedComment #90
cilefen CreditAttribution: cilefen commentedComment #91
cilefen CreditAttribution: cilefen commentedComment #92
joelpittetI forgot how amazing this was for D7 and lost this patch. Reapplied #9 and wanted to share:
The scenario is a cart page with cache primed.
I'll see what I can do about getting some profiling results on D8 site.
Comment #93
joelpittetOf course I don't have as many modules enabled in D8 as I did in D7 so the gain is not as amazing.
Scenario: I turned page/dynamic page caching off, enabled all the core modules + xhprof + devel modules and rebuilt the registry and hit /admin/modules
The function doesn't get called as many times compared to the other but the time % diff is still nice:)
15% wall time improvement with a 7.5% memory regression seems to be a good trade off to me:
Comment #94
joelpittetHere's an
is_file()
diff for the same scenario except there were more calls on theadmin/content
page from what looks like views.Comment #95
catchIt looks like something else is wrong that we're checking the existence of views.views_execution.inc 164 times on admin/content - do you know what's causing that? Are we really doing that for 164 different views hooks or something?
Comment #96
joelpittet@catch I think you mis-read that picture.
is_file()
is called 164 times diff.views.views_execution.inc
has 0 diff. Those are only ever checked once.Comment #97
alexpottWe checking for the existence of MODULE.views_execution.inc for every installed module multiple times because we fire these...
\Drupal::moduleHandler()->invokeAll('views_pre_view', array($this, $display_id, &$this->args));
$module_handler->invokeAll('views_pre_build', array($this));
\Drupal::moduleHandler()->invokeAll('views_query_alter', array($view, $this));
$query->addMetaData('views_substitutions', \Drupal::moduleHandler()->invokeAll('views_query_substitutions', array($this->view)));
$module_handler->invokeAll('views_post_build', array($this));
$module_handler->invokeAll('views_pre_execute', array($this));
$module_handler->invokeAll('views_post_execute', array($this));
$module_handler->invokeAll('views_pre_render', array($this));
$module_handler->invokeAll('views_post_render', array($this, &$this->display_handler->output, $cache));
$substitutions = \Drupal::moduleHandler()->invokeAll('views_form_substitutions');
We also try to load MODULE.views.inc for every installed module multiple times because of the alter
$this->moduleHandler->alter($this->alterHook, $definitions);
where the alter hooks areviews_plugins_field
views_plugins_filter
views_plugins_relationship
views_plugins_area
views_plugins_exposed_form
views_plugins_query
views_plugins_pager
views_plugins_join
views_plugins_cache
So for every module installed once views is installed you get 17 unnecessary is_file() checks on a cache rebuild.
Comment #98
catchYes sorry #95 should've said MODULE.views_extension.inc, thanks for right answers to my wrong question.
This is another reason to get rid of hook_hook_info() per #2233261: Deprecate hook_hook_info() although that's 9.x material unfortunately.
In the meantime we ought to be able to check the file existence once per hook group per module per request, if we do that, I don't see what the static cache gets us in 8.x and would be tempted to move this directly to 7.x. Not changing status just yet though.
Comment #99
alexpottI think given how ModuleHandler::buildImplementationInfo() works the current solution to add static caching to ModuleHandler::loadInclude() might be the simplest. I'm not sure we should change module_load_include() - in fact I think we should open an issue to deprecate the function for 9.x and replace all of the usages in core where possible - the only place where it is not possible is the installer and for that we could just copy&rename the function and put it in install.core.inc.
Also we should store the results when is_file() returns FALSE - this way we would get 731 less calls to is_file() on admin/content as opposed to 165.
Comment #100
alexpottHere's a patch that stores the FALSE results of is_file()
The difference on a cache rebuild on admin/content:
Comment #101
alexpottThe other advantage of caching the FALSE results of is_file() is that PHP doesn't cache information about non-existent files so funnily enough this is probably saving more disk access than the patch in #91.
Comment #102
joelpittetSetting back to RTBC, thanks @alexpott nice extra touch.
Comment #103
joelpittetre #99 This looks like the removal issue #697946: Properly deprecate module_load_include() and move it into \Drupal::moduleHandler() service
Comment #105
catchI've committed/pushed this to 8.1.x, without the module_load_include() hunk though since that doesn't seem relevant to 8.x if we cache the not-found files, and if we convert usages away and deprecate it too.
Moving to 7.x for backport, I don't think this is worth an 8.0.x backport (we'd have to deal with @internal-izing the protected property) - but if someone strongly feels it should go into 8.0.x then feel free to set to that version.
Comment #106
cilefen CreditAttribution: cilefen commentedComment #107
tstoecklerIf it's good enough for D8, I guess it's good enough for D7 too. I verified that the code matches.
Comment #108
pounardFor Drupal 7, there is a possible variant without static cache, which is half slower when xdebug is enabled, but in par with static cache with opcache and without xdebug (did a few local benchmarks involving including something like 400 PHP files 10000 times):
Comment #109
alexpott@pounard what happens if the included file has a PHP error in? I think this would a change of behaviour.
Comment #110
pounardThat's an excellent question, I think that this would mask the warnings only if the code executes directly PHP code, or potentially mask fatal parsing errors (although I'm not sure about those) but it won't otherwise; but it needs maybe some more research or testing about this.
Comment #111
catchBack to needs review for choosing either option.
Comment #112
pounard@alexpott's was a good one, and I think using the @ operator would indeed mask PHP errors when parsing the loaded files, I think it's a dangerous behaviour change. I'm sad we cannot get rid of all the is_file() calls but I don't see a better alternative than the static cache right now.
Comment #113
jonhattanIndeed
@
operator silences errors when the included file has any syntax error. Back to RTBCComment #114
pounardNot exactly, the static cache should only be populated if the drupal_get_path() function exists: in case a file load attempt is done before Drupal is fully bootstrapped, it won't be able to load it once it is fully bootstrapped: so this must be changed in order to ensure that it can.
should become:
Comment #115
jonhattan#106 doesn't populate the cache when drupal_get_path() is not available.
Comment #116
pounardOh yeah right, I misread, I don't know how I could miss that.
Comment #117
David_Rothstein CreditAttribution: David_Rothstein as a volunteer commentedStatically caching the FALSE results gives me some pause, since it's certainly possible to download code and load/run it all in the same page request.... But I think in practice this isn't likely to be a problem, and it's essentially the direction we're headed in #1081266: Avoid re-scanning module directory when a filename or a module is missing anyway.
Committed to 7.x - thanks!
This issue has the "needs backport to D6" tag, but I'm guessing that's not happening at this point. Feel free to reopen for Drupal 6 if you want to try :)
Comment #119
ndobromirov CreditAttribution: ndobromirov at FFW commentedJust a thought,
As we are keeping a static cache now and we can not include a file more than once, why are we still using the require_once instead of require only.
This comes from an old project that had to use Drupal through an NFS mount. And there were this kinds of issues:
https://bugs.php.net/bug.php?id=52312
https://echo.co/blog/speed-php-nfs-turborealpath
We can open this in a follow-up, as it's pretty straight forward.
BR,
Nikolay Dobromirov.
Comment #120
tstoeckler@ndobromirov: I think that's a great idea! Can you open a follow-up? Then we could discuss that change properly. Thanks!
Comment #121
pjcdawkins CreditAttribution: pjcdawkins commented@ndobromirov changing from
require_once()
sounds problematic. The file might already have beenrequire()
d by other code (it's often a good idea but it's by no means compulsory to usemodule_load_include()
).Comment #122
pounardI agree with #121, it does not sounds very wise to switch to require.
Comment #123
ndobromirov CreditAttribution: ndobromirov at FFW commentedThe follow-up is here: https://www.drupal.org/node/2663228
There is a POC patch to see whether this will pass D7 core tests.
@pjcdawkins I did not consider this (#121) but it's a valid point. We will see what the test bot will say :).
Comment #124
pounardfollow-up or not, modules might require files by themselves, even if core passes tests, a lot of module may not.
Comment #125
FluxSauce CreditAttribution: FluxSauce at Four Kitchens for Meredith Corporation commentedJust wanted to say thank you.