diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index a686ec6..3da1a1c 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -27,7 +27,7 @@
  * @deprecated Use \Drupal\Core\Entity\EntityManager::getDefinitions() directly.
  */
 function entity_get_info($entity_type = NULL) {
-  if (empty($entity_type)) {
+  if (!isset($entity_type)) {
     return drupal_container()->get('plugin.manager.entity')->getDefinitions();
   }
   else {
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/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.
