When Drupal includes files, it almost always test the file existence on disk. While I understand why this could have been a good idea, it is in reality almost always useless.

drupal_get_filename() relies on the system_list() function cache, which is prepopulated the first system_list() hit. Meanwhile, drupal_load() function relies on drupal_get_filename().

Those functions are called only in the context where both the module exists and the system database table is complete (and cached). The file_exists() calls are redundant and useless.

This has serious drawbacks, when you use a slow filesystem with a good OPCode cache, all file system calls are supposedly routed to the OPCode cache instead when including PHP files, which actually lives the file system alone. This is specifically true when you disable the stat command to be run by the OPCode cache.

Doing excessive file_exists() calls, you force the file system to do it twice (if the OPCode cache does stat() calls) and you explicitely wake up the file system. This is a serious performance problem especially on slow file systems.

This is quite a stupid thing to do, considering that Drupal knows the module list at all time, has it into database, and moreover does cache it with the system_list() function.

What I propose here is a simple patch that keeps the backward compatibility for contrib module, by adding new functions:

  • module_load_system_include() as a replacement of module_load_include(), fully relying on system_list() cache, with no hits into the database nor the filesystem. Keeping the module_load_include() as-is for modules using it to attempt stupid file discovery using it.
  • drupal_get_system_filename() as a replacement of drupal_get_filename(), fully relying on system_list() cache, with no hits into the database nor the filesystem. Keeping the drupal_get_filename() as-is for modules using it to attempt stupid file discovery using it.
  • Some hacks inside the legacy functions to speed them up, testing first with the above functions if the file exists before trying to do any file system access.

And changing those details:

  • At install time, prepopulates some essential modules into system_list() cache while the database isn't up.
  • At install time, when database is up, fallback on normal behavior.
  • Changed the module_enable() function behavior, clears the system_list() cache before actually loading the module files, which will allow the drupal_load() function to rely on a fresh rebuilt cache. This has no incidence on the core behavior.
  • Aggregated both bootstrap and others system_list() cache entries into one, in order to ensure we'll have one and only one SQL or cache backend query in order to fetch all needed information.

Then fixes all core modules to use them in order to avoid excessive file_exists() calls (except in the update module which does some tricky install stuff without being properly enabled).

While doing this on a Vanilla core, this saves a lot of file system direct access. Even better, with a complex a big site, using Drupal Commerce, Rules, Views, CTools, etc... most of them have been ripped of, and everything is working fine, including install.

Notice that the core has a quite invasive patch, in order to have a positive impact all core modules must use the new functions and not the old ones.

Everything else works gracefully, legacy functions remain in order for contrib to have the right behavior.

Comments

pounard’s picture

Status: Active » Needs review
StatusFileSize
new30.8 KB

Here is the patch.

EDIT: Please note that I don't want to apply this to D8. While this is a great performance boost for D7 on slow filesystems, D8 is on the move with module handling, with tasks such as making the .module file optional, which by design disrupt this patch methodology.

Status: Needs review » Needs work

The last submitted patch, 1715222-1-d7-remove_useless_file_stat.patch, failed testing.

pounard’s picture

Tests are failing, but normal runtime is not. My guess is that simpletest has some problems.

pounard’s picture

Just some numbers: on a D7 vanilla install using the standard profile, hitting the home page, no content, as uid 1 will reduce the number of file_exists() calls from 63 to 34. Considering that file_exists() calls number is proportional to the enabled modules count, this is significant as soon as you have a slow FS (especially network storage).

martijn houtman’s picture

On my setup with about 175 modules enabled, is_file() is being called 981 times, according to XHProf. It appears to be responsible for about 3.3% of the total Wall time. The above patch reduces the time spent on loading the page, but when applied, XHProf no longer works, and I have no idea why.

Anyhow, this patch is worth the research on large sites with lots of modules.

pounard’s picture

3% of Wall time, you probably have a very efficient file system! 1000 FS access is way too many.

martijn houtman’s picture

No, I think the rest of the execution is just dead slow ;-)

But yeah, it's way too many. Once the files are in place, Drupal should not have to check every time. That's what this patch is about, right?

pounard’s picture

It is!

Chris Charlton’s picture

It seems like this ticket got stalled?

pounard’s picture

Pretty much, but I think that recent commits already fixed a few of these.

Might worth the shot to see how the 7.x latest behave, but I don't have any time soon to do this.

joseph.olstad’s picture

Subscribing, it'd be great if this patch was rerolled for the latest 7.x dev and some performance profiling was ran against it (xhprof).

joseph.olstad’s picture

Removing file_exists was already done in 8.x , is ready for backport, see related issues

stefan.r’s picture

Issue tags: +Performance

Status: Needs work » Closed (outdated)

Automatically closed because Drupal 7 security and bugfix support has ended as of 5 January 2025. If the issue verifiably applies to later versions, please reopen with details and update the version.