diff --git includes/module.inc includes/module.inc
index 994c669..131ac56 100644
--- includes/module.inc
+++ includes/module.inc
@@ -376,18 +376,25 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
   }
 
   if (!isset($implementations[$hook])) {
+    $hook_info = module_hook_info();
     $implementations[$hook] = array();
     $list = module_list(FALSE, FALSE, $sort);
     foreach ($list as $module) {
-      if (module_hook($module, $hook)) {
-        $implementations[$hook][$module] = $module;
+      $include_file = FALSE;
+      if (module_hook($module, $hook) || (isset($hook_info[$hook]['group']) && $include_file = module_load_include('inc', $module, "$module.{$hook_info[$hook]['group']}") && module_hook($module, $hook))) {
+        $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
         // We added something to the cache, so write it when we are done.
         $implementations['#write_cache'] = TRUE;
       }
     }
   }
   else {
-    foreach ($implementations[$hook] as $module) {
+    foreach ($implementations[$hook] as $module => $group) {
+      // If this hook implementation is stored in a lazy-loaded file,
+      // include that file first.
+      if ($group) {
+        module_load_include('inc', $module, "$module.$group");
+      }
       // 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.
@@ -400,13 +407,46 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
     }
   }
 
-  // The explicit cast forces a copy to be made. This is needed because
-  // $implementations[$hook] is only a reference to an element of
-  // $implementations and if there are nested foreaches (due to nested node
-  // API calls, for example), they would both manipulate the same array's
-  // references, which causes some modules' hooks not to be called.
-  // See also http://www.zend.com/zend/art/ref-count.php.
-  return (array)$implementations[$hook];
+  return array_keys($implementations[$hook]);
+}
+
+/**
+ * Retrieve a list of what hooks are explicitly declared.
+ */
+function module_hook_info() {
+  $hook_info = &drupal_static(__FUNCTION__, array());
+
+  if (empty($hook_info)) {
+    $cache = cache_get('hook_info');
+    if ($cache === FALSE) {
+      // rebuild the cache and save it.
+
+      // We can't use module_invoke_all() here or it would cause
+      // an infinite loop.
+      foreach (module_list() as $module) {
+        $function = $module . '_hook_info';
+        if (function_exists($function)) {
+          $result = $function();
+          if (isset($result) && is_array($result)) {
+            $hook_info = array_merge_recursive($hook_info, $result);
+          }
+        }
+      }
+      // We can't use drupal_alter() for the same reason as above.
+      foreach (module_list() as $module) {
+        $function = $module . '_hook_info_alter';
+        if (function_exists($function)) {
+          $function($hook_info);
+        }
+      }
+      cache_set('hook_info', $hook_info);
+    }
+    else {
+      $hook_info = $cache->data;
+    }
+  }
+
+  return $hook_info;
 }
 
 /**
diff --git includes/token.inc includes/token.inc
index 6f832b7..6eb7e2a 100644
--- includes/token.inc
+++ includes/token.inc
@@ -153,7 +153,6 @@ function token_scan($text) {
 function token_generate($type, array $tokens, array $data = array(), array $options = array()) {
   $results = array();
   $options += array('sanitize' => TRUE);
-  _token_initialize();
 
   $result = module_invoke_all('tokens', $type, $tokens, $data, $options);
   foreach ($result as $original => $replacement) {
@@ -227,25 +226,8 @@ function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') {
 function token_info() {
   $data = &drupal_static(__FUNCTION__);
   if (!isset($data)) {
-    _token_initialize();
     $data = module_invoke_all('token_info');
     drupal_alter('token_info', $data);
   }
   return $data;
 }
-
-/**
- * Load modulename.tokens.inc for all enabled modules.
- */
-function _token_initialize() {
-  $initialized = &drupal_static(__FUNCTION__);
-  if (!$initialized) {
-    foreach (module_list() as $module) {
-      $filename = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$module.tokens.inc";
-      if (file_exists($filename)) {
-        include_once $filename;
-      }
-    }
-    $initialized = TRUE;
-  }
-}
diff --git modules/node/node.module modules/node/node.module
index 2c9c7f5..ff39178 100644
--- modules/node/node.module
+++ modules/node/node.module
@@ -117,50 +117,6 @@ function node_help($path, $arg) {
 }
 
 /**
- * Implement hook_theme().
- */
-function node_theme() {
-  return array(
-    'node' => array(
-      'arguments' => array('elements' => NULL),
-      'template' => 'node',
-    ),
-    'node_list' => array(
-      'arguments' => array('items' => NULL, 'title' => NULL),
-    ),
-    'node_search_admin' => array(
-      'arguments' => array('form' => NULL),
-    ),
-    'node_filter_form' => array(
-      'arguments' => array('form' => NULL),
-      'file' => 'node.admin.inc',
-    ),
-    'node_filters' => array(
-      'arguments' => array('form' => NULL),
-      'file' => 'node.admin.inc',
-    ),
-    'node_add_list' => array(
-      'arguments' => array('content' => NULL),
-      'file' => 'node.pages.inc',
-    ),
-    'node_form' => array(
-      'arguments' => array('form' => NULL),
-      'file' => 'node.pages.inc',
-    ),
-    'node_preview' => array(
-      'arguments' => array('node' => NULL),
-      'file' => 'node.pages.inc',
-    ),
-    'node_log_message' => array(
-      'arguments' => array('log' => NULL),
-    ),
-    'node_admin_overview' => array(
-      'arguments' => array('name' => NULL, 'type' => NULL),
-    ),
-  );
-}
-
-/**
  * Implement hook_cron().
  */
 function node_cron() {
@@ -1102,7 +1058,7 @@ function node_build($node, $build_mode = 'full') {
   $build = $node->content;
   // We don't need duplicate rendering info in node->content.
   unset($node->content);
-  
+
   $build += array(
     '#theme' => 'node',
     '#node' => $node,
@@ -1274,7 +1230,7 @@ function template_preprocess_node(&$variables) {
 
   // Flatten the node object's member fields.
   $variables = array_merge((array)$node, $variables);
-  
+
   // Helpful $content variable for templates.
   foreach (element_children($variables['elements']) as $key) {
     $variables['content'][$key] = $variables['elements'][$key];
@@ -1448,7 +1404,7 @@ function node_search_admin() {
   $form['content_ranking']['info'] = array(
     '#value' => '<em>' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '</em>'
   );
-  
+
   // Note: reversed to reflect that higher number = higher ranking.
   $options = drupal_map_assoc(range(0, 10));
   foreach (module_invoke_all('ranking') as $var => $values) {
@@ -1473,7 +1429,7 @@ function node_search_execute($keys = NULL) {
     ->condition('n.status', 1)
     ->addTag('node_access')
     ->searchExpression($keys, 'node');
-  
+
   // Insert special keywords.
   $query->setOption('type', 'n.type');
   $query->setOption('language', 'n.language');
@@ -1484,10 +1440,10 @@ function node_search_execute($keys = NULL) {
   if (!$query->executeFirstPass()) {
     return array();
   }
-  
+
   // Add the ranking expressions.
   _node_rankings($query);
-  
+
   // Add a count query.
   $inner_query = clone $query;
   $count_query = db_select($inner_query->fields('i', array('sid')));
@@ -1496,7 +1452,7 @@ function node_search_execute($keys = NULL) {
   $find = $query
     ->limit(10)
     ->execute();
-  
+
   // Load results.
   $results = array();
   foreach ($find as $item) {
@@ -1504,14 +1460,14 @@ function node_search_execute($keys = NULL) {
     $node = node_load($item->sid);
     node_build_content($node, 'search_result');
     $node->rendered = drupal_render($node->content);
-  
+
     // Fetch comments for snippet.
     $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node);
     // Fetch terms for snippet.
     $node->rendered .= ' ' . module_invoke('taxonomy', 'node_update_index', $node);
-  
+
     $extra = module_invoke_all('node_search_result', $node);
-  
+
     $results[] = array(
       'link' => url('node/' . $item->sid, array('absolute' => TRUE)),
       'type' => check_plain(node_type_get_name($node)),
diff --git modules/node/node.registry.inc modules/node/node.registry.inc
new file mode 100644
index 0000000..c654587
--- /dev/null
+++ modules/node/node.registry.inc
@@ -0,0 +1,51 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Hook implementations for the 'registry' group.
+ */
+
+/**
+ * Implement hook_theme().
+ */
+function node_theme() {
+  return array(
+    'node' => array(
+      'arguments' => array('elements' => NULL),
+      'template' => 'node',
+    ),
+    'node_list' => array(
+      'arguments' => array('items' => NULL, 'title' => NULL),
+    ),
+    'node_search_admin' => array(
+      'arguments' => array('form' => NULL),
+    ),
+    'node_filter_form' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'node.admin.inc',
+    ),
+    'node_filters' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'node.admin.inc',
+    ),
+    'node_add_list' => array(
+      'arguments' => array('content' => NULL),
+      'file' => 'node.pages.inc',
+    ),
+    'node_form' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'node.pages.inc',
+    ),
+    'node_preview' => array(
+      'arguments' => array('node' => NULL),
+      'file' => 'node.pages.inc',
+    ),
+    'node_log_message' => array(
+      'arguments' => array('log' => NULL),
+    ),
+    'node_admin_overview' => array(
+      'arguments' => array('name' => NULL, 'type' => NULL),
+    ),
+  );
+}
diff --git modules/system/system.api.php modules/system/system.api.php
index 16e09d7..d265f99 100644
--- modules/system/system.api.php
+++ modules/system/system.api.php
@@ -2481,5 +2481,30 @@ function hook_date_formats_alter(&$formats) {
 }
 
 /**
+ * Defines one or more hooks that are exposed by a module.
+ *
+ * Normally hooks do not need to be explicitly defined.  However,
+ * by declaring a hook explicitly a module may define a
+ * "hook group" for it.  Modules that implement a hook may
+ * then place their implementation in either $module.module or
+ * in $module.$group.inc.  If the hook is in the second file,
+ * that file will be lazy-loaded as needed.  In general, hooks
+ * that are rarely invoked and/or are very large should be placed
+ * in a separate include file while hooks that are very short
+ * or are very frequently called should be left in the main
+ * module file so that they are always available.
+ */
+function hook_hook_info() {
+  $hooks['menu'] = array(
+    'group' => 'registry',
+  );
+  $hooks['theme'] = array(
+    'group' => 'registry',
+  );
+
+  return $hooks;
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
diff --git modules/system/system.module modules/system/system.module
index 9172bb4..c6b55b7 100644
--- modules/system/system.module
+++ modules/system/system.module
@@ -2489,6 +2489,26 @@ function system_get_module_admin_tasks($module) {
 }
 
 /**
+ * Implement hook_hook_info();
+ */
+function system_hook_info() {
+  $hooks['menu'] = array(
+    'group' => 'registry',
+  );
+  $hooks['theme'] = array(
+    'group' => 'registry',
+  );
+  $hooks['token_info'] = array(
+    'group' => 'tokens',
+  );
+  $hooks['tokens'] = array(
+    'group' => 'tokens',
+  );
+
+  return $hooks;
+}
+
+/**
  * Implement hook_cron().
  *
  * Remove older rows from flood and batch table. Remove old temporary files.
