diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index 4ae754b..9a4264d 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -25,23 +25,11 @@
  * @see hook_entity_info_alter()
  */
 function entity_get_info($entity_type = NULL) {
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['entity_info'] = &drupal_static(__FUNCTION__);
-  }
-  $entity_info = &$drupal_static_fast['entity_info'];
-
-  if (empty($entity_info)) {
-    $entity_info = drupal_container()->get('plugin.manager.entity')->getDefinitions();
-  }
-
-  if (empty($entity_type)) {
-    return $entity_info;
-  }
-  elseif (isset($entity_info[$entity_type])) {
-    return $entity_info[$entity_type];
+  $manager = drupal_container()->get('plugin.manager.entity');
+  if (isset($entity_type)) {
+    return $manager->getDefinition($entity_type);
   }
+  return $manager->getDefinitions();
 }
 
 /**
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Component/Plugin/Discovery/AnnotatedClassDiscovery.php
index 7e31231..9dd12b9 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/AnnotatedClassDiscovery.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/AnnotatedClassDiscovery.php
@@ -19,6 +19,8 @@
  */
 class AnnotatedClassDiscovery implements DiscoveryInterface {
 
+  protected $definitions;
+
   /**
    * The namespaces within which to find plugin classes.
    *
@@ -56,15 +58,24 @@ function __construct($plugin_namespaces = array(), $annotation_namespaces = arra
    * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinition().
    */
   public function getDefinition($plugin_id) {
-    $plugins = $this->getDefinitions();
-    return isset($plugins[$plugin_id]) ? $plugins[$plugin_id] : NULL;
+    // Ensure to trigger the annotation-based class discovery only once.
+    if (!isset($this->definitions)) {
+      $this->definitions = $this->getDefinitions();
+    }
+    if (isset($this->definitions[$plugin_id])) {
+      return $this->definitions[$plugin_id];
+    }
   }
 
   /**
    * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions().
    */
   public function getDefinitions() {
-    $definitions = array();
+    // Ensure to trigger the annotation-based class discovery only once.
+    if (isset($this->definitions)) {
+      return $this->definitions;
+    }
+    $this->definitions = array();
     $reader = new AnnotationReader();
     // Prevent @endlink from being parsed as an annotation.
     $reader->addGlobalIgnoredName('endlink');
@@ -93,14 +104,14 @@ public function getDefinitions() {
                 // instead of requiring us to work with the annotation object.
                 $definition = $annotation->get();
                 $definition['class'] = $class;
-                $definitions[$definition['id']] = $definition;
+                $this->definitions[$definition['id']] = $definition;
               }
             }
           }
         }
       }
     }
-    return $definitions;
+    return $this->definitions;
   }
 
   /**
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/ProcessDecorator.php b/core/lib/Drupal/Component/Plugin/Discovery/ProcessDecorator.php
index fee6518..e074d96 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/ProcessDecorator.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/ProcessDecorator.php
@@ -50,10 +50,9 @@ public function __construct(DiscoveryInterface $decorated, $process_callback) {
    * Implements \Drupal\Component\Plugin\Discovery\DicoveryInterface::getDefinition().
    */
   public function getDefinition($plugin_id) {
-    $definitions = $this->getDefinitions();
-    if (isset($definitions[$plugin_id])) {
-      return $definitions[$plugin_id];
-    }
+    $definition = $this->decorated->getDefinition($plugin_id);
+    call_user_func_array($this->processCallback, array(&$definition, $plugin_id));
+    return $definition;
   }
 
   /**
diff --git a/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php b/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
index 168d5d7..5037f2c 100644
--- a/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
+++ b/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
@@ -30,11 +30,14 @@ class DefaultFactory implements FactoryInterface {
    */
   protected $discovery;
 
+  protected static $definitions = array();
+
   /**
    * Constructs a Drupal\Component\Plugin\Factory\DefaultFactory object.
    */
   public function __construct(DiscoveryInterface $discovery) {
     $this->discovery = $discovery;
+    static::$definitions = array();
   }
 
   /**
@@ -55,14 +58,20 @@ public function createInstance($plugin_id, array $configuration) {
    *
    *  @return string
    *    The appropriate class name.
+   *
+   * @todo Why is this static?
    */
   public static function getPluginClass($plugin_id, DiscoveryInterface $discovery) {
-    $plugin_definition = $discovery->getDefinition($plugin_id);
-    if (empty($plugin_definition['class'])) {
+    // @todo As long as this is static, this definition cache will break badly,
+    //   since plugin IDs are namespaced by owner.
+    if (!isset(static::$definitions[$plugin_id])) {
+      static::$definitions[$plugin_id] = $discovery->getDefinition($plugin_id);
+    }
+    if (empty(static::$definitions[$plugin_id]['class'])) {
       throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id));
     }
 
-    $class = $plugin_definition['class'];
+    $class = static::$definitions[$plugin_id]['class'];
 
     if (!class_exists($class)) {
       throw new PluginException(sprintf('Plugin (%s) instance class "%s" does not exist.', $plugin_id, $class));
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index c760707..10cff75 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -12,6 +12,7 @@
 use Drupal\Component\Plugin\Discovery\ProcessDecorator;
 use Drupal\Core\Plugin\Discovery\AlterDecorator;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\CacheDecorator;
 use Drupal\Core\Plugin\Discovery\InfoHookDecorator;
 use Drupal\Core\Cache\CacheBackendInterface;
 
@@ -130,34 +131,6 @@
 class EntityManager extends PluginManagerBase {
 
   /**
-   * The cache bin used for entity plugin definitions.
-   *
-   * @var string
-   */
-  protected $cacheBin = 'cache';
-
-  /**
-   * The cache key used for entity plugin definitions.
-   *
-   * @var string
-   */
-  protected $cacheKey = 'entity_info';
-
-  /**
-   * The cache expiration for entity plugin definitions.
-   *
-   * @var int
-   */
-  protected $cacheExpire = CacheBackendInterface::CACHE_PERMANENT;
-
-  /**
-   * The cache tags used for entity plugin definitions.
-   *
-   * @var array
-   */
-  protected $cacheTags = array('entity_info' => TRUE);
-
-  /**
    * Contains instantiated controllers keyed by controller type and entity type.
    *
    * @var array
@@ -197,36 +170,8 @@ public function __construct() {
     $this->discovery = new InfoHookDecorator($this->discovery, 'entity_info');
     $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
     $this->discovery = new AlterDecorator($this->discovery, 'entity_info');
+    $this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . language(LANGUAGE_TYPE_INTERFACE)->langcode, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
     $this->factory = new DefaultFactory($this);
-
-    // Entity type plugins includes translated strings, so each language is
-    // cached separately.
-    $this->cacheKey .= ':' . language(LANGUAGE_TYPE_INTERFACE)->langcode;
-  }
-
-  /**
-   * Overrides Drupal\Component\Plugin\PluginManagerBase::getDefinition().
-   */
-  public function getDefinition($plugin_id) {
-    $definitions = $this->getDefinitions();
-    return isset($definitions[$plugin_id]) ? $definitions[$plugin_id] : NULL;
-  }
-
-  /**
-   * Overrides Drupal\Component\Plugin\PluginManagerBase::getDefinitions().
-   */
-  public function getDefinitions() {
-    // Because \Drupal\Core\Plugin\Discovery\CacheDecorator runs before
-    // definitions are processed and does not support cache tags, we perform our
-    // own caching.
-    if ($cache = cache($this->cacheBin)->get($this->cacheKey)) {
-      return $cache->data;
-    }
-    else {
-      $definitions = parent::getDefinitions();
-      cache($this->cacheBin)->set($this->cacheKey, $definitions, $this->cacheExpire, $this->cacheTags);
-      return $definitions;
-    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/AlterDecorator.php b/core/lib/Drupal/Core/Plugin/Discovery/AlterDecorator.php
index 8bcee3f..09f191b 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/AlterDecorator.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/AlterDecorator.php
@@ -46,11 +46,9 @@ public function __construct(DiscoveryInterface $decorated, $hook) {
    * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinition().
    */
   public function getDefinition($plugin_id) {
-    $definitions = $this->getDefinitions();
-    return isset($definitions[$plugin_id]) ? $definitions[$plugin_id] : NULL;
+    return $this->decorated->getDefinition($plugin_id);
   }
 
-
   /**
    * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions().
    */
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/CacheDecorator.php b/core/lib/Drupal/Core/Plugin/Discovery/CacheDecorator.php
index a199171..ef62a45 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/CacheDecorator.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/CacheDecorator.php
@@ -85,23 +85,26 @@ public function __construct(DiscoveryInterface $decorated, $cache_key, $cache_bi
 
   /**
    * Implements Drupal\Component\Plugin\Discovery\DicoveryInterface::getDefinition().
+   *
+   * @todo The required logic here is almost impossible to resolve. Merge
+   *   CacheDecorator either into DefaultFactory or into PluginManagerBase.
    */
   public function getDefinition($plugin_id) {
-    // Optimize for fast access to definitions if they are already in memory.
-    if (isset($this->definitions)) {
-      // Avoid using a ternary that would create a copy of the array.
-      if (isset($this->definitions[$plugin_id])) {
-        return $this->definitions[$plugin_id];
+    if (!isset($this->definitions[$plugin_id])) {
+      // Consult the (complete) cache (once).
+      if (!isset($this->definitions)) {
+        $this->getCachedDefinitions();
       }
-      else {
-        return;
+      // If it still does not exist, move up the decorator stack.
+      if (!isset($this->definitions[$plugin_id])) {
+        $this->definitions[$plugin_id] = $this->decorated->getDefinition($plugin_id);
+        // @todo Defer to shutdown/termination/end of request.
+        $this->setCachedDefinitions($this->definitions);
       }
     }
-
-    $definitions = $this->getDefinitions();
     // Avoid using a ternary that would create a copy of the array.
-    if (isset($definitions[$plugin_id])) {
-      return $definitions[$plugin_id];
+    if (isset($this->definitions[$plugin_id])) {
+      return $this->definitions[$plugin_id];
     }
   }
 
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php b/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php
index 43aad82..71b4e75 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Type/LegacyDiscoveryDecorator.php
@@ -47,8 +47,14 @@ public function __construct(DiscoveryInterface $decorated) {
    * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinition().
    */
   public function getDefinition($plugin_id) {
-    $definitions = $this->getDefinitions();
-    return isset($definitions[$plugin_id]) ? $definitions[$plugin_id] : NULL;
+    $definition = $this->decorated->getDefinition($plugin_id);
+    if (!isset($definition)) {
+      $legacy_definitions = $this->getLegacyDefinitions();
+      if (isset($legacy_definitions[$plugin_id])) {
+        return $legacy_definitions[$plugin_id];
+      }
+    }
+    return $definition;
   }
 
   /**
@@ -56,7 +62,12 @@ public function getDefinition($plugin_id) {
    */
   public function getDefinitions() {
     $definitions = $this->decorated->getDefinitions();
+    $definitions += $this->getLegacyDefinitions();
+    return $definitions;
+  }
 
+  protected function getLegacyDefinitions() {
+    $definitions = array();
     $legacy_discovery = new HookDiscovery($this->hook);
     if ($legacy_definitions = $legacy_discovery->getDefinitions()) {
       foreach ($legacy_definitions as $plugin_id => $definition) {
diff --git a/core/modules/menu/lib/Drupal/menu/Plugin/Derivative/MenuBlock.php b/core/modules/menu/lib/Drupal/menu/Plugin/Derivative/MenuBlock.php
index 6f9b4ac..97e15c5 100644
--- a/core/modules/menu/lib/Drupal/menu/Plugin/Derivative/MenuBlock.php
+++ b/core/modules/menu/lib/Drupal/menu/Plugin/Derivative/MenuBlock.php
@@ -20,7 +20,11 @@ class MenuBlock extends SystemMenuBlock {
    * Implements \Drupal\Component\Plugin\Derivative\DerivativeInterface::getDerivativeDefinitions().
    */
   public function getDerivativeDefinitions(array $base_plugin_definition) {
+    if (isset($this->derivatives)) {
+      return $this->derivatives;
+    }
     // Provide block plugin definitions for all user-defined (custom) menus.
+    $this->derivatives = array();
     foreach (menu_get_menus(FALSE) as $menu => $name) {
       $this->derivatives[$menu] = $base_plugin_definition;
       $this->derivatives[$menu]['subject'] = $name;
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index 7e26d50..1e102ab 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -165,6 +165,7 @@ function menu_menu() {
  * Implements hook_entity_info_alter().
  */
 function menu_entity_info_alter(&$entity_info) {
+  $entity_info['menu']['module'] = 'menu';
   $entity_info['menu']['list_controller_class'] = 'Drupal\menu\MenuListController';
   $entity_info['menu']['uri_callback'] = 'menu_uri';
   $entity_info['menu']['form_controller_class'] = array(
diff --git a/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php
index 8123732..1883003 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/Derivative/SystemMenuBlock.php
@@ -21,24 +21,29 @@ class SystemMenuBlock implements DerivativeInterface {
    *
    * @var array
    */
-  protected $derivatives = array();
+  protected $derivatives;
 
   /**
    * Implements \Drupal\Component\Plugin\Derivative\DerivativeInterface::getDerivativeDefinition().
    */
   public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
-    if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
+    if (!isset($this->derivatives)) {
+      $this->getDerivativeDefinitions($base_plugin_definition);
+    }
+    if (isset($this->derivatives[$derivative_id])) {
       return $this->derivatives[$derivative_id];
     }
-    $this->getDerivativeDefinitions($base_plugin_definition);
-    return $this->derivatives[$derivative_id];
   }
 
   /**
    * Implements \Drupal\Component\Plugin\Derivative\DerivativeInterface::getDerivativeDefinitions().
    */
   public function getDerivativeDefinitions(array $base_plugin_definition) {
+    if (isset($this->derivatives)) {
+      return $this->derivatives;
+    }
     // Provide a block plugin definition for each system menu.
+    $this->derivatives = array();
     foreach (menu_list_system_menus() as $menu => $name) {
       // The block deltas need to be prefixed with 'menu-', since the 'main'
       // menu would otherwise clash with the 'main' page content block.
