diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php
index d402d70b54..a0269f5301 100644
--- a/core/lib/Drupal/Core/Cache/MemoryBackend.php
+++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php
@@ -64,26 +64,21 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
    * Checks that items are either permanent or did not expire, and returns data
    * as appropriate.
    *
-   * @param object $cache
+   * @param array $cache
    *   An item loaded from self::get() or self::getMultiple().
    * @param bool $allow_invalid
    *   (optional) If TRUE, cache items may be returned even if they have expired
    *   or been invalidated.
    *
-   * @return mixed
+   * @return object|false
    *   The item with data as appropriate or FALSE if there is no
    *   valid item to load.
    */
-  protected function prepareItem($cache, $allow_invalid) {
-    if (!isset($cache->data)) {
+  protected function prepareItem(array $cache, $allow_invalid) {
+    if (!isset($cache['data'])) {
       return FALSE;
     }
-    // The object passed into this function is the one stored in $this->cache.
-    // We must clone it as part of the preparation step so that the actual
-    // cache object is not affected by the unserialize() call or other
-    // manipulations of the returned object.
-
-    $prepared = clone $cache;
+    $prepared = (object) $cache;
     $prepared->data = unserialize($prepared->data);
 
     // Check expire time.
@@ -104,7 +99,10 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = []) {
     $tags = array_unique($tags);
     // Sort the cache tags so that they are stored consistently in the database.
     sort($tags);
-    $this->cache[$cid] = (object) [
+
+    // Do not create an object at this point to minimize the number of objects
+    // garbage collection has to keep a track off.
+    $this->cache[$cid] = [
       'cid' => $cid,
       'data' => serialize($data),
       'created' => $this->getRequestTime(),
@@ -148,7 +146,7 @@ public function deleteAll() {
    */
   public function invalidate($cid) {
     if (isset($this->cache[$cid])) {
-      $this->cache[$cid]->expire = $this->getRequestTime() - 1;
+      $this->cache[$cid]['expire'] = $this->getRequestTime() - 1;
     }
   }
 
@@ -158,7 +156,7 @@ public function invalidate($cid) {
   public function invalidateMultiple(array $cids) {
     $items = array_intersect_key($this->cache, array_flip($cids));
     foreach ($items as $cid => $item) {
-      $this->cache[$cid]->expire = $this->getRequestTime() - 1;
+      $this->cache[$cid]['expire'] = $this->getRequestTime() - 1;
     }
   }
 
@@ -167,8 +165,8 @@ public function invalidateMultiple(array $cids) {
    */
   public function invalidateTags(array $tags) {
     foreach ($this->cache as $cid => $item) {
-      if (array_intersect($tags, $item->tags)) {
-        $this->cache[$cid]->expire = $this->getRequestTime() - 1;
+      if (array_intersect($tags, $item['tags'])) {
+        $this->cache[$cid]['expire'] = $this->getRequestTime() - 1;
       }
     }
   }
@@ -178,7 +176,7 @@ public function invalidateTags(array $tags) {
    */
   public function invalidateAll() {
     foreach ($this->cache as $cid => $item) {
-      $this->cache[$cid]->expire = $this->getRequestTime() - 1;
+      $this->cache[$cid]['expire'] = $this->getRequestTime() - 1;
     }
   }
 
diff --git a/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
index 3271108211..841e4568a0 100644
--- a/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
+++ b/core/lib/Drupal/Core/Cache/MemoryCache/MemoryCache.php
@@ -8,40 +8,29 @@
 /**
  * Defines a memory cache implementation.
  *
- * Stores cache items in memory using a PHP array.
+ * Stores cache items in memory using a PHP array. Cache data is not serialized
+ * thereby returning the same object as was cached.
  *
  * @ingroup cache
  */
 class MemoryCache extends MemoryBackend implements MemoryCacheInterface {
 
   /**
-   * Prepares a cached item.
-   *
-   * Checks that items are either permanent or did not expire, and returns data
-   * as appropriate.
-   *
-   * @param object $cache
-   *   An item loaded from self::get() or self::getMultiple().
-   * @param bool $allow_invalid
-   *   (optional) If TRUE, cache items may be returned even if they have expired
-   *   or been invalidated. Defaults to FALSE.
-   *
-   * @return mixed
-   *   The item with data as appropriate or FALSE if there is no
-   *   valid item to load.
+   * {@inheritdoc}
    */
-  protected function prepareItem($cache, $allow_invalid = FALSE) {
-    if (!isset($cache->data)) {
+  protected function prepareItem(array $cache, $allow_invalid = FALSE) {
+    if (!isset($cache['data'])) {
       return FALSE;
     }
+    $prepared = (object) $cache;
     // Check expire time.
-    $cache->valid = $cache->expire == static::CACHE_PERMANENT || $cache->expire >= $this->getRequestTime();
+    $prepared->valid = $prepared->expire == static::CACHE_PERMANENT || $prepared->expire >= $this->getRequestTime();
 
-    if (!$allow_invalid && !$cache->valid) {
+    if (!$allow_invalid && !$prepared->valid) {
       return FALSE;
     }
 
-    return $cache;
+    return $prepared;
   }
 
   /**
@@ -51,8 +40,11 @@ public function set($cid, $data, $expire = MemoryCacheInterface::CACHE_PERMANENT
     assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
     $tags = array_unique($tags);
 
-    $this->cache[$cid] = (object) [
+    // Do not create an object at this point to minimize the number of objects
+    // garbage collection has to keep a track off.
+    $this->cache[$cid] = [
       'cid' => $cid,
+      // Note that $data is not serialized.
       'data' => $data,
       'created' => $this->getRequestTime(),
       'expire' => $expire,
diff --git a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php
index dd4ae5f470..cea42f49b2 100644
--- a/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php
+++ b/core/lib/Drupal/Core/Installer/InstallerServiceProvider.php
@@ -14,7 +14,7 @@
  * $conf['container_service_providers'] and required to prevent various services
  * from trying to retrieve data from storages that do not exist yet.
  */
-class InstallerServiceProvider implements ServiceProviderInterface, ServiceModifierInterface {
+class InstallerServiceProvider extends NormalInstallerServiceProvider implements ServiceProviderInterface, ServiceModifierInterface {
 
   /**
    * {@inheritdoc}
@@ -26,18 +26,12 @@ public function register(ContainerBuilder $container) {
     $container->register('config.storage', 'Drupal\Core\Config\InstallStorage');
 
     // Replace services with in-memory implementations.
-    $definition = $container->getDefinition('cache_factory');
-    $definition->setClass('Drupal\Core\Cache\MemoryBackendFactory');
-    $definition->setArguments([]);
-    $definition->setMethodCalls([]);
     $container
       ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory');
     $container
       ->register('keyvalue.expirable', 'Drupal\Core\KeyValueStore\KeyValueNullExpirableFactory');
 
     // Replace services with no-op implementations.
-    $container
-      ->register('lock', 'Drupal\Core\Lock\NullLockBackend');
     $container
       ->register('url_generator', 'Drupal\Core\Routing\NullGenerator')
       ->addArgument(new Reference('request_stack'));
@@ -46,12 +40,6 @@ public function register(ContainerBuilder $container) {
     $container
       ->register('router.dumper', 'Drupal\Core\Routing\NullMatcherDumper');
 
-    // Remove the cache tags invalidator tag from the cache tags storage, so
-    // that we don't call it when cache tags are invalidated very early in the
-    // installer.
-    $container->getDefinition('cache_tags.invalidator.checksum')
-      ->clearTag('cache_tags_invalidator');
-
     // Replace the route builder with an empty implementation.
     // @todo Convert installer steps into routes; add an installer.routing.yml.
     $definition = $container->getDefinition('router.builder');
@@ -60,8 +48,7 @@ public function register(ContainerBuilder $container) {
       // we don't need to ship with a custom proxy class.
       ->setLazy(FALSE);
 
-    // Use a performance optimised module extension list.
-    $container->getDefinition('extension.list.module')->setClass('Drupal\Core\Installer\InstallerModuleExtensionList');
+    parent::register($container);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Installer/NormalInstallerServiceProvider.php b/core/lib/Drupal/Core/Installer/NormalInstallerServiceProvider.php
index 472d984b22..9127260b37 100644
--- a/core/lib/Drupal/Core/Installer/NormalInstallerServiceProvider.php
+++ b/core/lib/Drupal/Core/Installer/NormalInstallerServiceProvider.php
@@ -2,11 +2,23 @@
 
 namespace Drupal\Core\Installer;
 
+use Drupal\Core\Cache\MemoryBackendFactory;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DependencyInjection\ServiceProviderInterface;
+use Drupal\Core\Lock\NullLockBackend;
 
 /**
- * Service provider for the non early installer environment.
+ * Service provider for the installer environment.
+ *
+ * This class is manually added by install_begin_request() via
+ * $conf['container_service_providers'] and optimizes the container for
+ * installation.
+ *
+ * Note that the early installer environment requires the further customisations
+ * in InstallerServiceProvider.
+ *
+ * @see install_begin_request()
+ * @see \Drupal\Core\Installer\InstallerServiceProvider
  */
 class NormalInstallerServiceProvider implements ServiceProviderInterface {
 
@@ -14,10 +26,29 @@ class NormalInstallerServiceProvider implements ServiceProviderInterface {
    * {@inheritdoc}
    */
   public function register(ContainerBuilder $container) {
-    // Use a performance optimised module extension list.
-    $container->getDefinition('extension.list.module')->setClass('Drupal\Core\Installer\InstallerModuleExtensionList');
-    $container->getDefinition('extension.list.theme')->setClass('Drupal\Core\Installer\InstallerThemeExtensionList');
-    $container->getDefinition('extension.list.theme_engine')->setClass('Drupal\Core\Installer\InstallerThemeEngineExtensionList');
+    // Replace cache services with in-memory implementations. The results in
+    // less queries to set caches which will only be cleared on the next module
+    // install.
+    $definition = $container->getDefinition('cache_factory');
+    $definition->setClass(MemoryBackendFactory::class);
+    $definition->setArguments([]);
+    $definition->setMethodCalls([]);
+
+    // Replace lock service with no-op implementation as Drupal installation can
+    // only occur in a single thread and the site should not be publicly
+    // available.
+    $container
+      ->register('lock', NullLockBackend::class);
+
+    // Remove the cache tags invalidator tag from the cache tags storage, so
+    // that we don't call it when cache tags are invalidated in the installer.
+    $container->getDefinition('cache_tags.invalidator.checksum')
+      ->clearTag('cache_tags_invalidator');
+
+    // Use performance-optimized extension lists.
+    $container->getDefinition('extension.list.module')->setClass(InstallerModuleExtensionList::class);
+    $container->getDefinition('extension.list.theme')->setClass(InstallerThemeExtensionList::class);
+    $container->getDefinition('extension.list.theme_engine')->setClass(InstallerThemeEngineExtensionList::class);
   }
 
 }
