Index: update.php
===================================================================
RCS file: /cvs/drupal/drupal/update.php,v
retrieving revision 1.303
diff -u -p -r1.303 update.php
--- update.php	14 Sep 2009 07:33:55 -0000	1.303
+++ update.php	26 Sep 2009 19:04:02 -0000
@@ -292,6 +292,10 @@ if (empty($op) && $update_access_allowed
   drupal_load('module', 'system');
   drupal_load('module', 'filter');
 
+  // Reset the module_implements() cache so that any new hook implementations
+  // in updated code are picked up.
+  module_implements('', FALSE, TRUE);
+
   // Set up $language, since the installer components require it.
   drupal_language_initialize();
 
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.995
diff -u -p -r1.995 common.inc
--- includes/common.inc	25 Sep 2009 15:20:12 -0000	1.995
+++ includes/common.inc	26 Sep 2009 19:04:02 -0000
@@ -2405,6 +2405,7 @@ function drupal_page_footer() {
 
   _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
   drupal_cache_system_paths();
+  module_implements_write_cache();
 }
 
 /**
Index: includes/module.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/module.inc,v
retrieving revision 1.157
diff -u -p -r1.157 module.inc
--- includes/module.inc	24 Aug 2009 00:14:18 -0000	1.157
+++ includes/module.inc	26 Sep 2009 19:14:00 -0000
@@ -14,7 +14,6 @@ function module_load_all($bootstrap = FA
   foreach (module_list(TRUE, $bootstrap) as $module) {
     drupal_load('module', $module);
   }
-  module_implements('', FALSE, TRUE);
 }
 
 /**
@@ -336,27 +335,67 @@ function module_hook($module, $hook) {
  * @param $sort
  *   By default, modules are ordered by weight and filename, settings this option
  *   to TRUE, module list will be ordered by module name.
- * @param $refresh
+ * @param $reset
  *   For internal use only: Whether to force the stored list of hook
  *   implementations to be regenerated (such as after enabling a new module,
  *   before processing hook_enable).
+ *
  * @return
  *   An array with the names of the modules which are implementing this hook.
+ *
+ * @see module_implements_write_cache().
  */
-function module_implements($hook, $sort = FALSE, $refresh = FALSE) {
-  static $implementations;
+function module_implements($hook, $sort = FALSE, $reset = FALSE) {
+  $implementations = &drupal_static(__FUNCTION__, array());
 
-  if ($refresh) {
+  // We maintain a persistent cache of hook implementations in addition to the
+  // static cache to avoid looping through every module and every hook on each
+  // request. Benchmarks show that the benefit of this caching outweighs the
+  // additional database hit even when using the default database caching
+  // backend and only a small number of modules are enabled. The cost of the
+  // cache_get() is more or less constant and reduced further when non-database
+  // caching backends are used, so there will be more significant gains when a
+  // large number of modules are installed or hooks invoked, since this can
+  // quickly lead to module_hook() being called several thousand times
+  // per request.
+  if ($reset) {
     $implementations = array();
+    cache_set('module_implements', array());
     return;
   }
 
+  // Fetch implementations from cache.
+  if (empty($implementations)) {
+    $implementations = cache_get('module_implements');
+    if ($implementations === FALSE) {
+      $implementations = array();
+    }
+    else {
+      $implementations = $implementations->data;
+    }
+  }
+
   if (!isset($implementations[$hook])) {
     $implementations[$hook] = array();
     $list = module_list(FALSE, FALSE, $sort);
     foreach ($list as $module) {
       if (module_hook($module, $hook)) {
-        $implementations[$hook][] = $module;
+        $implementations[$hook][$module] = $module;
+        // We added something to the cache, so write it when we are done.
+        $implementations['#write_cache'] = TRUE;
+      }
+    }
+  }
+  else {
+    foreach ($implementations[$hook] as $module) {
+      // It is possible that a module removed a hook implementation without the
+      // implementations cache being rebuilt yet, so we check module_hook() on
+      // each request to avoid undefined function errors.
+      if (!module_hook($module, $hook)) {
+        // Clear out the stale implementation from the cache and force a cache
+        // refresh to forget about no longer existing hook implementations.
+        unset($implementations[$hook][$module]);
+        $implementations['#write_cache'] = TRUE;
       }
     }
   }
@@ -371,6 +410,24 @@ function module_implements($hook, $sort 
 }
 
 /**
+ * Writes the hook implementation cache.
+ *
+ * @see module_implements()
+ */
+function module_implements_write_cache() {
+  $implementations = &drupal_static('module_implements');
+  // Check whether we need to write the cache. We do not want to cache hooks,
+  // which are only invoked on HTTP POST requests. Maybe the page is not
+  // cacheable for other reasons than the HTTP request type as well, but this
+  // does not matter for the hook implementation cache, because nothing breaks
+  // if hook implementations are not cached.
+  if (isset($implementations['#write_cache']) && drupal_page_is_cacheable()) {
+    unset($implementations['#write_cache']);
+    cache_set('module_implements', $implementations);
+  }
+}
+
+/**
  * Invoke a hook in a particular module.
  *
  * @param $module
