diff --git includes/module.inc includes/module.inc
index 994c669..39fbd91 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..86ba876 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)),
@@ -1688,189 +1644,6 @@ function _node_add_access() {
 }
 
 /**
- * Implement hook_menu().
- */
-function node_menu() {
-  $items['admin/content'] = array(
-    'title' => 'Content',
-    'description' => 'Find and manage content.',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('node_admin_content'),
-    'access arguments' => array('administer nodes'),
-    'weight' => -10,
-    'file' => 'node.admin.inc',
-  );
-  $items['admin/content/node'] = array(
-    'title' => 'Content',
-    'description' => "View, edit, and delete your site's content.",
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => -10,
-  );
-
-  $items['admin/reports/status/rebuild'] = array(
-    'title' => 'Rebuild permissions',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('node_configure_rebuild_confirm'),
-    // Any user than can potentially trigger a node_access_needs_rebuild(TRUE)
-    // has to be allowed access to the 'node access rebuild' confirm form.
-    'access arguments' => array('access administration pages'),
-    'type' => MENU_CALLBACK,
-    'file' => 'node.admin.inc',
-  );
-
-  $items['admin/structure/types'] = array(
-    'title' => 'Content types',
-    'description' => 'Manage posts by content type, including default status, front page promotion, comment settings, etc.',
-    'page callback' => 'node_overview_types',
-    'access arguments' => array('administer content types'),
-    'file' => 'content_types.inc',
-  );
-  $items['admin/structure/types/list'] = array(
-    'title' => 'List',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => -10,
-  );
-  $items['admin/structure/types/add'] = array(
-    'title' => 'Add content type',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('node_type_form'),
-    'access arguments' => array('administer content types'),
-    'type' => MENU_LOCAL_ACTION,
-    'file' => 'content_types.inc',
-  );
-  $items['admin/structure/types/manage/%node_type'] = array(
-    'title' => 'Edit content type',
-    'title callback' => 'node_type_page_title',
-    'title arguments' => array(4),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('node_type_form', 4),
-    'access arguments' => array('administer content types'),
-    'file' => 'content_types.inc',
-  );
-  $items['admin/structure/types/manage/%node_type/edit'] = array(
-    'title' => 'Edit',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-  );
-  $items['admin/structure/types/manage/%node_type/delete'] = array(
-    'title' => 'Delete',
-    'page arguments' => array('node_type_delete_confirm', 4),
-    'access arguments' => array('administer content types'),
-    'type' => MENU_CALLBACK,
-    'file' => 'content_types.inc',
-  );
-
-  $items['node'] = array(
-    'title' => 'Content',
-    'page callback' => 'node_page_default',
-    'access arguments' => array('access content'),
-    'type' => MENU_CALLBACK,
-  );
-  $items['node/add'] = array(
-    'title' => 'Add new content',
-    'page callback' => 'node_add_page',
-    'access callback' => '_node_add_access',
-    'weight' => 1,
-    'menu_name' => 'management',
-    'theme callback' => '_node_custom_theme',
-    'file' => 'node.pages.inc',
-  );
-  $items['rss.xml'] = array(
-    'title' => 'RSS feed',
-    'page callback' => 'node_feed',
-    'access arguments' => array('access content'),
-    'type' => MENU_CALLBACK,
-  );
-  // @todo Remove this loop when we have a 'description callback' property.
-  // Reset internal static cache of _node_types_build(), forces to rebuild the
-  // node type information.
-  drupal_static_reset('_node_types_build');
-  foreach (node_type_get_types() as $type) {
-    $type_url_str = str_replace('_', '-', $type->type);
-    $items['node/add/' . $type_url_str] = array(
-      'title' => $type->name,
-      'title callback' => 'check_plain',
-      'page callback' => 'node_add',
-      'page arguments' => array(2),
-      'access callback' => 'node_access',
-      'access arguments' => array('create', $type->type),
-      'description' => $type->description,
-      'file' => 'node.pages.inc',
-    );
-  }
-  $items['node/%node'] = array(
-    'page callback' => 'node_page_view',
-    'page arguments' => array(1),
-    'access callback' => 'node_access',
-    'access arguments' => array('view', 1),
-    'type' => MENU_CALLBACK);
-  $items['node/%node/view'] = array(
-    'title' => 'View',
-    'type' => MENU_DEFAULT_LOCAL_TASK,
-    'weight' => -10);
-  $items['node/%node/edit'] = array(
-    'title' => 'Edit',
-    'page callback' => 'node_page_edit',
-    'page arguments' => array(1),
-    'access callback' => 'node_access',
-    'access arguments' => array('update', 1),
-    'theme callback' => '_node_custom_theme',
-    'weight' => 1,
-    'type' => MENU_LOCAL_TASK,
-    'file' => 'node.pages.inc',
-  );
-  $items['node/%node/delete'] = array(
-    'title' => 'Delete',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('node_delete_confirm', 1),
-    'access callback' => 'node_access',
-    'access arguments' => array('delete', 1),
-    'weight' => 1,
-    'type' => MENU_CALLBACK,
-    'file' => 'node.pages.inc',
-  );
-  $items['node/%node/revisions'] = array(
-    'title' => 'Revisions',
-    'page callback' => 'node_revision_overview',
-    'page arguments' => array(1),
-    'access callback' => '_node_revision_access',
-    'access arguments' => array(1),
-    'weight' => 2,
-    'type' => MENU_LOCAL_TASK,
-    'file' => 'node.pages.inc',
-  );
-  $items['node/%node/revisions/%/view'] = array(
-    'title' => 'Revisions',
-    'load arguments' => array(3),
-    'page callback' => 'node_show',
-    'page arguments' => array(1, TRUE),
-    'access callback' => '_node_revision_access',
-    'access arguments' => array(1),
-    'type' => MENU_CALLBACK,
-  );
-  $items['node/%node/revisions/%/revert'] = array(
-    'title' => 'Revert to earlier revision',
-    'load arguments' => array(3),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('node_revision_revert_confirm', 1),
-    'access callback' => '_node_revision_access',
-    'access arguments' => array(1, 'update'),
-    'type' => MENU_CALLBACK,
-    'file' => 'node.pages.inc',
-  );
-  $items['node/%node/revisions/%/delete'] = array(
-    'title' => 'Delete earlier revision',
-    'load arguments' => array(3),
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('node_revision_delete_confirm', 1),
-    'access callback' => '_node_revision_access',
-    'access arguments' => array(1, 'delete'),
-    'type' => MENU_CALLBACK,
-    'file' => 'node.pages.inc',
-  );
-  return $items;
-}
-
-/**
  * Title callback for a node type.
  */
 function node_type_page_title($type) {
diff --git modules/system/system.api.php modules/system/system.api.php
index 671a205..6708374 100644
--- modules/system/system.api.php
+++ modules/system/system.api.php
@@ -2507,5 +2507,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.
