diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index d77eca1..dbe2772 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -1718,6 +1718,11 @@ function hook_field_storage_details_alter(&$details, $field) {
  * objects in $entities. Fields with no values should be added as empty
  * arrays.
  *
+ * By the time this hook runs, the relevant field definitions have been
+ * populated and cached in FieldInfo, so calling field_info_field_by_id() on
+ * each field individually is more efficient than loading all fields in memory
+ * upfront with field_info_field_by_ids() (which is uncached).
+ *
  * @param $entity_type
  *   The type of entity, such as 'node' or 'user'.
  * @param $entities
@@ -1736,11 +1741,10 @@ function hook_field_storage_details_alter(&$details, $field) {
  *     loaded.
  */
 function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) {
-  $field_info = field_info_field_by_ids();
   $load_current = $age == FIELD_LOAD_CURRENT;
 
   foreach ($fields as $field_id => $ids) {
-    $field = $field_info[$field_id];
+    $field = field_info_field_by_id($field_id);
     $field_name = $field['field_name'];
     $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
 
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
index 611b577..53f390e 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -261,7 +261,6 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
     'langcode' => NULL,
   );
   $options += $default_options;
-  $field_info = field_info_field_by_ids();
 
   $fields = array();
   $grouped_instances = array();
@@ -285,7 +284,7 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
     foreach ($instances as $instance) {
       $field_id = $instance['field_id'];
       $field_name = $instance['field_name'];
-      $field = $field_info[$field_id];
+      $field = field_info_field_by_id($field_id);
       $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
       if (function_exists($function)) {
         // Add the field to the list of fields to invoke the hook on.
@@ -591,7 +590,6 @@ function field_attach_form($entity_type, EntityInterface $entity, &$form, &$form
  *     non-deleted fields are operated on.
  */
 function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) {
-  $field_info = field_info_field_by_ids();
   $load_current = $age == FIELD_LOAD_CURRENT;
 
   // Merge default options.
@@ -669,7 +667,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $
         }
         // Collect the storage backend if the field has not been loaded yet.
         if (!isset($skip_fields[$field_id])) {
-          $field = $field_info[$field_id];
+          $field = field_info_field_by_id($field_id);
           $storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid;
         }
       }
diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc
index b748c51..929a12c 100644
--- a/core/modules/field/field.crud.inc
+++ b/core/modules/field/field.crud.inc
@@ -323,7 +323,11 @@ function field_read_field($field_name, $include_additional = array()) {
  * Reads in fields that match an array of conditions.
  *
  * @param array $params
- *   An array of conditions to match against.
+ *   An array of conditions to match against. Keys are columns from the
+ *   'field_config' table, values are conditions to match. Additionally,
+ *   conditions on the 'entity_type' and 'bundle' columns from the
+ *   'field_config_instance' table are supported (select fields having an
+ *   instance on a given bundle).
  * @param array $include_additional
  *   The default behavior of this function is to not return fields that
  *   are inactive or have been deleted. Setting
@@ -341,8 +345,18 @@ function field_read_fields($params = array(), $include_additional = array()) {
 
   // Turn the conditions into a query.
   foreach ($params as $key => $value) {
+    // Allow filtering on the 'entity_type' and 'bundle' columns of the
+    // field_config_instance table.
+    if ($key == 'entity_type' || $key == 'bundle') {
+      if (empty($fci_join)) {
+        $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id');
+      }
+      $key = 'fci.' . $key;
+    }
+
     $query->condition($key, $value);
   }
+
   if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
     $query
       ->condition('fc.active', 1)
diff --git a/core/modules/field/field.info.inc b/core/modules/field/field.info.inc
index a70796f..abcd186 100644
--- a/core/modules/field/field.info.inc
+++ b/core/modules/field/field.info.inc
@@ -5,6 +5,30 @@
  * Field Info API, providing information about available fields and field types.
  */
 
+use Drupal\field\FieldInfo;
+
+/**
+ * Retrieves the Drupal\field\FieldInfo object for the current request.
+ *
+ * @return Drupal\field\FieldInfo
+ *   An instance of the Drupal\field\FieldInfo class.
+ */
+function _field_info_field_cache() {
+  // Use the advanced drupal_static() pattern, since this is called very often.
+  static $drupal_static_fast;
+
+  if (!isset($drupal_static_fast)) {
+    $drupal_static_fast['field_info_field_cache'] = &drupal_static(__FUNCTION__);
+  }
+  $info = &$drupal_static_fast['field_info_field_cache'];
+
+  if (!isset($info)) {
+    $info = new FieldInfo();
+  }
+
+  return $info;
+}
+
 /**
  * @defgroup field_info Field Info API
  * @{
@@ -34,7 +58,7 @@ function field_info_cache_clear() {
   entity_info_cache_clear();
 
   _field_info_collate_types_reset();
-  _field_info_collate_fields_reset();
+  _field_info_field_cache()->flush();
 }
 
 /**
@@ -167,289 +191,6 @@ function _field_info_collate_types_reset() {
 }
 
 /**
- * Collates all information on existing fields and instances.
- *
- * @return
- *   An associative array containing:
- *   - fields: Array of existing fields, keyed by field ID. This element
- *     lists deleted and non-deleted fields, but not inactive ones.
- *     Each field has an additional element, 'bundles', which is an array
- *     of all non-deleted instances of that field.
- *   - field_ids: Array of field IDs, keyed by field name. This element
- *     only lists non-deleted, active fields.
- *   - instances: Array of existing instances, keyed by entity type, bundle
- *     name and field name. This element only lists non-deleted instances
- *     whose field is active.
- *
- * @see _field_info_collate_fields_reset()
- */
-function _field_info_collate_fields() {
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  static $drupal_static_fast;
-
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['field_info_collate_fields'] = &drupal_static(__FUNCTION__);
-  }
-  $info = &$drupal_static_fast['field_info_collate_fields'];
-
-  if (!isset($info)) {
-    if ($cached = cache('field')->get('field_info_fields')) {
-      $info = $cached->data;
-    }
-    else {
-      $definitions = array(
-        'field_ids' => field_read_fields(array(), array('include_deleted' => 1)),
-        'instances' => field_read_instances(),
-      );
-
-      // Populate 'fields' with all fields, keyed by ID.
-      $info['fields'] = array();
-      foreach ($definitions['field_ids'] as $key => $field) {
-        $info['fields'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field);
-      }
-
-      // Build an array of field IDs for non-deleted fields, keyed by name.
-      $info['field_ids'] = array();
-      foreach ($info['fields'] as $key => $field) {
-        if (!$field['deleted']) {
-          $info['field_ids'][$field['field_name']] = $key;
-        }
-      }
-
-      // Populate 'instances'. Only non-deleted instances are considered.
-      $info['instances'] = array();
-      foreach (field_info_bundles() as $entity_type => $bundles) {
-        foreach ($bundles as $bundle => $bundle_info) {
-          $info['instances'][$entity_type][$bundle] = array();
-        }
-      }
-      foreach ($definitions['instances'] as $instance) {
-        $field = $info['fields'][$instance['field_id']];
-        $instance = _field_info_prepare_instance($instance, $field);
-        $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
-        // Enrich field definitions with the list of bundles where they have
-        // instances. NOTE: Deleted fields in $info['field_ids'] are not
-        // enriched because all of their instances are deleted, too, and
-        // are thus not in $definitions['instances'].
-        $info['fields'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle'];
-      }
-
-      // Populate 'extra_fields'.
-      $extra = module_invoke_all('field_extra_fields');
-      drupal_alter('field_extra_fields', $extra);
-      // Merge in saved settings.
-      foreach ($extra as $entity_type => $bundles) {
-        foreach ($bundles as $bundle => $extra_fields) {
-          $extra_fields = _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle);
-          $info['extra_fields'][$entity_type][$bundle] = $extra_fields;
-        }
-      }
-
-      cache('field')->set('field_info_fields', $info);
-    }
-  }
-
-  return $info;
-}
-
-/**
- * Clear collated information on existing fields and instances.
- */
-function _field_info_collate_fields_reset() {
-  drupal_static_reset('_field_info_collate_fields');
-  cache('field')->delete('field_info_fields');
-}
-
-/**
- * Prepares a field definition for the current run-time context.
- *
- * Since the field was last saved or updated, new field settings can be
- * expected.
- *
- * @param $field
- *   The raw field structure as read from the database.
- */
-function _field_info_prepare_field($field) {
-  // Make sure all expected field settings are present.
-  $field['settings'] += field_info_field_settings($field['type']);
-  $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
-
-  // Add storage details.
-  $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field);
-  drupal_alter('field_storage_details', $details, $field, $instance);
-  $field['storage']['details'] = $details;
-
-  // Initialize the 'bundles' list.
-  $field['bundles'] = array();
-
-  return $field;
-}
-
-/**
- * Prepares an instance definition for the current run-time context.
- *
- * Since the instance was last saved or updated, a number of things might have
- * changed: widgets or formatters disabled, new settings expected, new view
- * modes added...
- *
- * @param $instance
- *   The raw instance structure as read from the database.
- * @param $field
- *   The field structure for the instance.
- *
- * @return
- *   Field instance array.
- */
-function _field_info_prepare_instance($instance, $field) {
-  // Make sure all expected instance settings are present.
-  $instance['settings'] += field_info_instance_settings($field['type']);
-
-  // Set a default value for the instance.
-  if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) {
-    $instance['default_value'] = NULL;
-  }
-
-  $instance['widget'] = _field_info_prepare_instance_widget($field, $instance['widget']);
-
-  foreach ($instance['display'] as $view_mode => $display) {
-    $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display);
-  }
-
-  // Fallback to 'hidden' for view modes configured to use custom display
-  // settings, and for which the instance has no explicit settings.
-  $entity_info = entity_get_info($instance['entity_type']);
-  $view_modes = array_merge(array('default'), array_keys($entity_info['view modes']));
-  $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
-  foreach ($view_modes as $view_mode) {
-    if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) {
-      if (!isset($instance['display'][$view_mode])) {
-        $instance['display'][$view_mode] = array(
-          'type' => 'hidden',
-          'label' => 'above',
-          'settings' => array(),
-          'weight' => 0,
-        );
-      }
-    }
-  }
-
-  return $instance;
-}
-
-/**
- * Adapts display specifications to the current run-time context.
- *
- * @param $field
- *   The field structure for the instance.
- * @param $display
- *   Display specifications as found in
- *   $instance['display']['some_view_mode'].
- */
-function _field_info_prepare_instance_display($field, $display) {
-  $field_type = field_info_field_types($field['type']);
-
-  // Fill in default values.
-  $display += array(
-    'label' => 'above',
-    'type' => $field_type['default_formatter'],
-    'settings' => array(),
-    'weight' => 0,
-  );
-  if ($display['type'] != 'hidden') {
-    $formatter_type = field_info_formatter_types($display['type']);
-    // Fallback to default formatter if formatter type is not available.
-    if (!$formatter_type) {
-      $display['type'] = $field_type['default_formatter'];
-      $formatter_type = field_info_formatter_types($display['type']);
-    }
-    $display['module'] = $formatter_type['module'];
-    // Fill in default settings for the formatter.
-    $display['settings'] += field_info_formatter_settings($display['type']);
-  }
-
-  return $display;
-}
-
-/**
- * Prepares widget specifications for the current run-time context.
- *
- * @param $field
- *   The field structure for the instance.
- * @param $widget
- *   Widget specifications as found in $instance['widget'].
- */
-function _field_info_prepare_instance_widget($field, $widget) {
-  $field_type = field_info_field_types($field['type']);
-
-  // Fill in default values.
-  $widget += array(
-    'type' => $field_type['default_widget'],
-    'settings' => array(),
-    'weight' => 0,
-  );
-
-  $widget_type = field_info_widget_types($widget['type']);
-  // Fallback to default formatter if formatter type is not available.
-  if (!$widget_type) {
-    $widget['type'] = $field_type['default_widget'];
-    $widget_type = field_info_widget_types($widget['type']);
-  }
-  $widget['module'] = $widget_type['module'];
-  // Fill in default settings for the widget.
-  $widget['settings'] += field_info_widget_settings($widget['type']);
-
-  return $widget;
-}
-
-/**
- * Prepares 'extra fields' for the current run-time context.
- *
- * @param $extra_fields
- *   The array of extra fields, as collected in hook_field_extra_fields().
- * @param $entity_type
- *   The entity type.
- * @param $bundle
- *   The bundle name.
- */
-function _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle) {
-  $entity_type_info = entity_get_info($entity_type);
-  $bundle_settings = field_bundle_settings($entity_type, $bundle);
-  $extra_fields += array('form' => array(), 'display' => array());
-
-  $result = array();
-  // Extra fields in forms.
-  foreach ($extra_fields['form'] as $name => $field_data) {
-    $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array();
-    if (isset($settings['weight'])) {
-      $field_data['weight'] = $settings['weight'];
-    }
-    $result['form'][$name] = $field_data;
-  }
-
-  // Extra fields in displayed entities.
-  $data = $extra_fields['display'];
-  foreach ($extra_fields['display'] as $name => $field_data) {
-    $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array();
-    $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes']));
-    foreach ($view_modes as $view_mode) {
-      if (isset($settings[$view_mode])) {
-        $field_data['display'][$view_mode] = $settings[$view_mode];
-      }
-      else {
-        $field_data['display'][$view_mode] = array(
-          'weight' => $field_data['weight'],
-          'visible' => TRUE,
-        );
-      }
-    }
-    unset($field_data['weight']);
-    $result['display'][$name] = $field_data;
-  }
-
-  return $result;
-}
-
-/**
  * Determines the behavior of a widget with respect to an operation.
  *
  * @param $op
@@ -596,20 +337,19 @@ function field_info_bundles($entity_type = NULL) {
 /**
  * Returns all field definitions.
  *
+ * This function does not read from cached data, and the returned array
+ * can be costly memory-wise. When iterating over the fields present in a given
+ * bundle after a call to field_info_instances($entity_type, $bundle), it is
+ * recommended to use field_info_field() on each individual field instead.
+ *
  * @return
  *   An array of field definitions, keyed by field name. Each field has an
  *   additional property, 'bundles', which is an array of all the bundles to
  *   which this field belongs keyed by entity type.
  */
 function field_info_fields() {
-  $fields = array();
-  $info = _field_info_collate_fields();
-  foreach ($info['fields'] as $key => $field) {
-    if (!$field['deleted']) {
-      $fields[$field['field_name']] = $field;
-    }
-  }
-  return $fields;
+  $cache = _field_info_field_cache();
+  return $cache->getFields();
 }
 
 /**
@@ -630,10 +370,8 @@ function field_info_fields() {
  * @see field_info_field_by_id()
  */
 function field_info_field($field_name) {
-  $info = _field_info_collate_fields();
-  if (isset($info['field_ids'][$field_name])) {
-    return $info['fields'][$info['field_ids'][$field_name]];
-  }
+  $cache = _field_info_field_cache();
+  return $cache->getField($field_name);
 }
 
 /**
@@ -651,17 +389,17 @@ function field_info_field($field_name) {
  * @see field_info_field()
  */
 function field_info_field_by_id($field_id) {
-  $info = _field_info_collate_fields();
-  if (isset($info['fields'][$field_id])) {
-    return $info['fields'][$field_id];
-  }
+  $cache = _field_info_field_cache();
+  return $cache->getFieldById($field_id);
 }
 
 /**
  * Returns the same data as field_info_field_by_id() for every field.
  *
- * This function is typically used when handling all fields of some entities
- * to avoid thousands of calls to field_info_field_by_id().
+ * This function does not read from cached data, and the returned array
+ * can be costly memory-wise. When iterating over the fields present in a given
+ * bundle after a call to field_info_instances($entity_type, $bundle), it is
+ * recommended to use field_info_field_by_id() on each individual field instead.
  *
  * @return
  *   An array, each key is a field ID and the values are field arrays as
@@ -672,17 +410,23 @@ function field_info_field_by_id($field_id) {
  * @see field_info_field_by_id()
  */
 function field_info_field_by_ids() {
-  $info = _field_info_collate_fields();
-  return $info['fields'];
+  $cache = _field_info_field_cache();
+  return $cache->getFieldsById();
 }
 
 /**
  * Retrieves information about field instances.
  *
+ * When retrieving the instances of a specific bundle (i.e. when both
+ * $entity_type and $bundle_name are provided, the function also populates a
+ * static chache with the corresponding field definitions, allowing a fast
+ * retrieval of field_info_field() later in the request.
+ *
  * @param $entity_type
- *   The entity type for which to return instances.
+ *   (optional) The entity type for which to return instances.
  * @param $bundle_name
- *   The bundle name for which to return instances.
+ *   (optional) The bundle name for which to return instances. If $entity_type
+ *   is NULL, the $bundle_name parameter is ignored.
  *
  * @return
  *   If $entity_type is not set, return all instances keyed by entity type and
@@ -691,22 +435,26 @@ function field_info_field_by_ids() {
  *   all instances for that bundle.
  */
 function field_info_instances($entity_type = NULL, $bundle_name = NULL) {
-  $info = _field_info_collate_fields();
+  $cache = _field_info_field_cache();
 
-  if (isset($entity_type) && isset($bundle_name)) {
-    return isset($info['instances'][$entity_type][$bundle_name]) ? $info['instances'][$entity_type][$bundle_name] : array();
+  if (!isset($entity_type)) {
+    return $cache->getInstances();
   }
-  elseif (isset($entity_type)) {
-    return isset($info['instances'][$entity_type]) ? $info['instances'][$entity_type] : array();
-  }
-  else {
-    return $info['instances'];
+  if (!isset($bundle_name)) {
+    return $cache->getInstances($entity_type);
   }
+
+  $info = $cache->getBundleInfo($entity_type, $bundle_name);
+  return $info['instances'];
 }
 
 /**
  * Returns an array of instance data for a specific field and bundle.
  *
+ * The function populates a static cache with all fields and instances used in
+ * the bundle, allowing a fast retrieval of field_info_field() or
+ * field_info_instance() later in the request.
+ *
  * @param $entity_type
  *   The entity type for the instance.
  * @param $field_name
@@ -715,9 +463,10 @@ function field_info_instances($entity_type = NULL, $bundle_name = NULL) {
  *   The bundle name for the instance.
  */
 function field_info_instance($entity_type, $field_name, $bundle_name) {
-  $info = _field_info_collate_fields();
-  if (isset($info['instances'][$entity_type][$bundle_name][$field_name])) {
-    return $info['instances'][$entity_type][$bundle_name][$field_name];
+  $cache = _field_info_field_cache();
+  $info = $cache->getBundleInfo($entity_type, $bundle_name);
+  if (isset($info['instances'][$field_name])) {
+    return $info['instances'][$field_name];
   }
 }
 
@@ -775,9 +524,11 @@ function field_info_instance($entity_type, $field_name, $bundle_name) {
  *   The array of pseudo-field elements in the bundle.
  */
 function field_info_extra_fields($entity_type, $bundle, $context) {
-  $info = _field_info_collate_fields();
-  if (isset($info['extra_fields'][$entity_type][$bundle][$context])) {
-    return $info['extra_fields'][$entity_type][$bundle][$context];
+  $cache = _field_info_field_cache();
+  $info = $cache->getBundleInfo($entity_type, $bundle);
+
+  if (isset($info['extra_fields'][$context])) {
+    return $info['extra_fields'][$context];
   }
   return array();
 }
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 8157971..0fa8d27 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -897,7 +897,8 @@ function field_view_field($entity_type, $entity, $field_name, $display = array()
   if ($field = field_info_field($field_name)) {
     if (is_array($display)) {
       // When using custom display settings, fill in default values.
-      $display = _field_info_prepare_instance_display($field, $display);
+      $cache = _field_info_field_cache();
+      $display = $cache->prepareInstanceDisplay($display, $field["type"]);
     }
 
     // Hook invocations are done through the _field_invoke() functions in
diff --git a/core/modules/field/lib/Drupal/field/FieldInfo.php b/core/modules/field/lib/Drupal/field/FieldInfo.php
new file mode 100644
index 0000000..678e493
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/FieldInfo.php
@@ -0,0 +1,564 @@
+<?php
+
+/*
+ * @file
+ * Definition of Drupal\field\FieldInfo.
+ */
+
+namespace Drupal\field;
+
+/**
+ * Provides field and instance definitions for the current runtime environment.
+ *
+ * A Drupal\field\FieldInfo object is created and statically persisted through
+ * the request by the field_info_cache() function. The object properties act as
+ * a "static cache" of fields and instances definitions. The information is
+ * loaded on demand, per bundle, from persistent cache entries, storing both
+ * fields and instances for a given bundle. Fields used in multiple bundles are
+ * replicated in several cache entries, and are merged into a single list in the
+ * memory cache.
+ *
+ * Cache entries are loaded for bundles as a whole, optimizing memory and CPU
+ * usage for the most common pattern of iterating over all instances of a bundle
+ * (and then probably accessing each corresponding field) rather than accessing
+ * a single instance. The specific caching strategy and interdependencies
+ * between fields and instances make the DrupalCacheArray class unfit here.
+ *
+ * The getFields(), getFieldsById() and getInstances(), methods, that retrieve
+ * all fields or all instances across all bundles, do not read from the caches,
+ * and do not populate the caches either. As such, they should be used
+ * sparingly.
+ */
+class FieldInfo {
+
+  /**
+   * Lightweight map of fields across entity types and bundles.
+   *
+   * @var array
+   */
+  protected $fieldMap;
+
+  /**
+   * List of $field structures keyed by ID. Includes deleted fields.
+   *
+   * @var array
+   */
+  protected $fieldsById = array();
+
+  /**
+   * Mapping of field names to the ID of the corresponding non-deleted field.
+   *
+   * @var array
+   */
+  protected $fieldIdsByName = array();
+
+  /**
+   * Contents of bundles ($instance definitions and extra fields).
+   *
+   * @var array
+   */
+  protected $bundleInfo = array();
+
+  const MISSING_FIELD_ID = -1;
+
+  /**
+   * Clears the "static" and persistent caches.
+   */
+  public function flush() {
+    $this->fieldMap = NULL;
+    $this->fieldsById = array();
+    $this->fieldIdsByName = array();
+    $this->bundleInfo = array();
+    cache('field')->deletePrefix('field_info:');
+  }
+
+  /**
+   * Collects a lightweight map of fields across bundles.
+   *
+   * @return
+   *   An array keyed by field name. Each value is an array with entity
+   *   types as keys and the array of bundle names as values.
+   */
+  protected function getFieldMap() {
+    // Read from the "static" cache.
+    if ($this->fieldMap !== NULL) {
+      return $this->fieldMap;
+    }
+
+    // Read from persistent cache.
+    if ($cached = cache('field')->get('field_info:field_map')) {
+      $map = $cached->data;
+
+      // Save in "static" cache.
+      $this->fieldMap = $map;
+
+      return $map;
+    }
+
+    $map = array();
+
+    $query = db_select('field_config_instance', 'fci');
+    $query->join('field_config', 'fc', 'fc.id = fci.field_id');
+    $query->fields('fci', array('field_name', 'entity_type', 'bundle'))
+      ->condition('fc.active', 1)
+      ->condition('fc.storage_active', 1)
+      ->condition('fc.deleted', 0)
+      ->condition('fci.deleted', 0);
+    foreach ($query->execute() as $row) {
+      $map[$row->field_name][$row->entity_type][] = $row->bundle;
+    }
+
+    // Save in "static" and persistent caches.
+    $this->fieldMap = $map;
+    cache('field')->set('field_info:field_map', $map);
+
+    return $map;
+  }
+
+  /**
+   * Returns all active, non-deleted fields.
+   *
+   * This method does not read from nor populate the "static" and persistent
+   * caches.
+   *
+   * @return
+   *   An array of field definitions, keyed by field name.
+   */
+  public function getFields() {
+    $fields = array();
+    foreach (field_read_fields() as $field) {
+      if (!$field['deleted']) {
+        $fields[$field['field_name']] = $this->prepareField($field);
+      }
+    }
+    return $fields;
+  }
+
+  /**
+   * Returns all active fields, including deleted ones.
+   *
+   * This method does not read from nor populate the "static" and persistent
+   * caches.
+   *
+   * @return
+   *   An array of field definitions, keyed by field ID.
+   */
+  public function getFieldsById() {
+    $fields = array();
+    foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) {
+      $fields[$field['id']] = $this->prepareField($field);
+    }
+    return $fields;
+  }
+
+  /**
+   * Retrieves all active, non-deleted instances definitions.
+   *
+   * This method does not read from nor populate the "static" and persistent
+   * caches.
+   *
+   * @param $entity_type
+   *   (optional) The entity type.
+   *
+   * @return
+   *   If $entity_type is not set, all instances keyed by entity type and bundle
+   *   name. If $entity_type is set, all instances for that entity type, keyed
+   *   by bundle name.
+   */
+  public function getInstances($entity_type = NULL) {
+    $instances = array();
+
+    // We need the field type for each instance. Fetch that directly from the
+    // database rather than loading all field definitions in memory with
+    // field_read_fields().
+    $field_types = db_query("SELECT field_name, type FROM {field_config} WHERE active = 1 AND storage_active = 1 AND deleted = 0")->fetchAllKeyed();
+
+    // Collect and prepare instances.
+    $params = isset($entity_type) ? array('entity_type' => $entity_type) : array();
+    foreach (field_read_instances($params) as $instance) {
+      $instance = $this->prepareInstance($instance, $field_types[$instance['field_name']]);
+      $instances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
+    }
+
+    if (isset($entity_type)) {
+      return isset($instances[$entity_type]) ? $instances[$entity_type] : array();
+    }
+    else {
+      return $instances;
+    }
+  }
+
+  /**
+   * Returns a field definition from a field name.
+   *
+   * This method only retrieves active, non-deleted fields.
+   *
+   * @param $field_name
+   *   The field name.
+   *
+   * @return
+   *   The field definition, or NULL if no field was found.
+   */
+  public function getField($field_name) {
+    // Read from the "static" cache.
+    if (isset($this->fieldIdsByName[$field_name]) || array_key_exists($field_name, $this->fieldIdsByName)) {
+      return $this->fieldsById[$this->fieldIdsByName[$field_name]];
+    }
+
+    // No persistent cache, fields are only persistently cached as part of a
+    // bundle.
+
+    // Cache miss: read from definition.
+    if ($field = field_read_field(array('field_name' => $field_name))) {
+      $field = $this->prepareField($field);
+
+      // Save in the "static" cache.
+      $this->fieldsById[$field['id']] = $field;
+      $this->fieldIdsByName[$field['field_name']] = $field['id'];
+
+      return $field;
+    }
+    else {
+      // Save in the "static" cache that this field does not exist.
+      $this->fieldsById[self::MISSING_FIELD_ID] = NULL;
+      $this->fieldIdsByName[$field_name] = self::MISSING_FIELD_ID;
+    }
+  }
+
+  /**
+   * Returns a field definition from a field ID.
+   *
+   * This method only retrieves active fields, deleted or not.
+   *
+   * @param $field_id
+   *   The field ID.
+   *
+   * @return
+   *   The field definition, or NULL if no field was found.
+   */
+  public function getFieldById($field_id) {
+    // Read from the "static" cache.
+    if (isset($this->fieldsById[$field_id]) || array_key_exists($field_id, $this->fieldsById)) {
+      return $this->fieldsById[$field_id];
+    }
+
+    // No persistent cache, fields are only persistently cached as part of a
+    // bundle.
+
+    // Cache miss: read from definition.
+    if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) {
+      $field = current($fields);
+      $field = $this->prepareField($field);
+
+      // Store in the static cache.
+      $this->fieldsById[$field['id']] = $field;
+      if (!$field['deleted']) {
+        $this->fieldIdsByName[$field['field_name']] = $field['id'];
+      }
+
+      return $field;
+    }
+    else {
+      $this->fieldsById[$field_id] = NULL;
+    }
+  }
+
+  /**
+   * Retrieves the instances and extra fields for a bundle.
+   *
+   * The function also populates the corressonding field definitions in the
+   * "static" cache.
+   *
+   * @param $entity_type
+   *   The entity type.
+   * @param $bundle
+   *   The bundle name.
+   *
+   * @return
+   *   An array with the following key/value pairs:
+   *   - instances: The list of instance definitions.
+   *   - extra_fields: The list extra fields.
+   */
+  public function getBundleInfo($entity_type, $bundle) {
+    // Read from the "static" cache.
+    if (isset($this->bundleInfo[$entity_type][$bundle]) || (isset($this->bundleInfo[$entity_type]) && array_key_exists($bundle, $this->bundleInfo[$entity_type]))) {
+      return $this->bundleInfo[$entity_type][$bundle];
+    }
+
+    // Make sure that the bundle info is initialized.
+    $this->bundleInfo[$entity_type][$bundle] = NULL;
+
+    // Read from the persistent cache.
+    if ($cached = cache('field')->get("field_info:bundle:$entity_type:$bundle")) {
+      $info = $cached->data;
+
+      // Extract the field definitions and save them in the "static" cache.
+      foreach ($info['fields'] as $field) {
+        if (!isset($this->fieldsById[$field['id']])) {
+          $this->fieldsById[$field['id']] = $field;
+          if (!$field['deleted']) {
+            $this->fieldIdsByName[$field['field_name']] = $field['id'];
+          }
+        }
+      }
+      unset($info['fields']);
+
+      // Save in the "static" cache.
+      $this->bundleInfo[$entity_type][$bundle] = $info;
+
+      return $info;
+    }
+
+    // Cache miss: collect from the definitions.
+
+    $info = array(
+      'instances' => array(),
+      'extra_fields' => array(),
+    );
+
+    // Collect the fields in the bundle.
+    $params = array('entity_type' => $entity_type, 'bundle' => $bundle);
+    $fields = field_read_fields($params);
+
+    // This iterates on non-deleted instances, so deleted fields are kept out of
+    // the persistent caches.
+    foreach (field_read_instances($params) as $instance) {
+      $field = $fields[$instance['field_name']];
+
+      $instance = $this->prepareInstance($instance, $field['type']);
+      $info['instances'][$field['field_name']] = $instance;
+
+      // If the field is not in our global "static" list yet, add it.
+      if (!isset($this->fieldsById[$field['id']])) {
+        $field = $this->prepareField($field);
+
+        $this->fieldsById[$field['id']] = $field;
+        $this->fieldIdsByName[$field['field_name']] = $field['id'];
+      }
+    }
+
+    // Populate 'extra_fields'. Note: given the current shape of
+    // hook_field_extra_fields(), we have no other way than collecting extra
+    // fields on all bundles.
+    $extra = module_invoke_all('field_extra_fields');
+    drupal_alter('field_extra_fields', $extra);
+    // Merge in saved settings.
+    if (isset($extra[$entity_type][$bundle])) {
+      $info['extra_fields'] = $this->prepareExtraFields($extra[$entity_type][$bundle], $entity_type, $bundle);
+    }
+
+    // Save in the "static" cache.
+    $this->bundleInfo[$entity_type][$bundle] = $info;
+
+    // The persistent cache additionally contains the definitions of the fields
+    // involved in the bundle.
+    $cache = $info + array('fields' => array());
+    foreach ($info['instances'] as $instance) {
+      $cache['fields'][] = $this->fieldsById[$instance['field_id']];
+    }
+    cache('field')->set("field_info:bundle:$entity_type:$bundle", $cache);
+
+    return $info;
+  }
+
+  /**
+   * Prepares a field definition for the current run-time context.
+   *
+   * @param $field
+   *   The raw field structure as read from the database.
+   *
+   * @return
+   *   The field definition completed for the current runtime context.
+   */
+  public function prepareField($field) {
+    // Make sure all expected field settings are present.
+    $field['settings'] += field_info_field_settings($field['type']);
+    $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
+
+    // Add storage details.
+    $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field);
+    drupal_alter('field_storage_details', $details, $field);
+    $field['storage']['details'] = $details;
+
+    // Populate the list of bundles using the field..
+    $field['bundles'] = array();
+    if (!$field['deleted']) {
+      $map = $this->getFieldMap();
+      if (isset($map[$field['field_name']])) {
+        $field['bundles'] = $map[$field['field_name']];
+      }
+    }
+
+    return $field;
+  }
+
+  /**
+   * Prepares an instance definition for the current run-time context.
+   *
+   * @param $instance
+   *   The raw instance structure as read from the database.
+   * @param $field_type
+   *   The field type.
+   *
+   * @return
+   *   The field instance array completed for the current runtime context.
+   */
+  public function prepareInstance($instance, $field_type) {
+    // Make sure all expected instance settings are present.
+    $instance['settings'] += field_info_instance_settings($field_type);
+
+    // Set a default value for the instance.
+    if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) {
+      $instance['default_value'] = NULL;
+    }
+
+    // Prepare widget settings.
+    $instance['widget'] = $this->prepareInstanceWidget($instance['widget'], $field_type);
+
+    // Prepare display settings.
+    foreach ($instance['display'] as $view_mode => $display) {
+      $instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type);
+    }
+
+    // Fall back to 'hidden' for view modes configured to use custom display
+    // settings, and for which the instance has no explicit settings.
+    $entity_info = entity_get_info($instance['entity_type']);
+    $view_modes = array_merge(array('default'), array_keys($entity_info['view modes']));
+    $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
+    foreach ($view_modes as $view_mode) {
+      if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) {
+        if (!isset($instance['display'][$view_mode])) {
+          $instance['display'][$view_mode] = array(
+            'type' => 'hidden',
+            'label' => 'above',
+            'settings' => array(),
+            'weight' => 0,
+          );
+        }
+      }
+    }
+
+    return $instance;
+  }
+
+  /**
+   * Prepares widget properties for the current run-time context.
+   *
+   * @param $widget
+   *   Widget specifications as found in $instance['widget'].
+   * @param $field_type
+   *   The field type.
+   *
+   * @return
+   *   The widget properties completed for the current runtime context.
+   */
+  public function prepareInstanceWidget($widget, $field_type) {
+    $field_type_info = field_info_field_types($field_type);
+
+    // Fill in default values.
+    $widget += array(
+      'type' => $field_type_info['default_widget'],
+      'settings' => array(),
+      'weight' => 0,
+    );
+
+    $widget_type_info = field_info_widget_types($widget['type']);
+    // Fall back to default formatter if formatter type is not available.
+    if (!$widget_type_info) {
+      $widget['type'] = $field_type_info['default_widget'];
+      $widget_type_info = field_info_widget_types($widget['type']);
+    }
+    $widget['module'] = $widget_type_info['module'];
+    // Fill in default settings for the widget.
+    $widget['settings'] += field_info_widget_settings($widget['type']);
+
+    return $widget;
+  }
+
+  /**
+   * Adapts display specifications to the current run-time context.
+   *
+   * @param $display
+   *   Display specifications as found in $instance['display']['a_view_mode'].
+   * @param $field_type
+   *   The field type.
+   *
+   * @return
+   *   The display properties completed for the current runtime context.
+   */
+  public function prepareInstanceDisplay($display, $field_type) {
+    $field_type_info = field_info_field_types($field_type);
+
+    // Fill in default values.
+    $display += array(
+      'label' => 'above',
+      'type' => $field_type_info['default_formatter'],
+      'settings' => array(),
+      'weight' => 0,
+    );
+    if ($display['type'] != 'hidden') {
+      $formatter_type_info = field_info_formatter_types($display['type']);
+      // Fall back to default formatter if formatter type is not available.
+      if (!$formatter_type_info) {
+        $display['type'] = $field_type_info['default_formatter'];
+        $formatter_type_info = field_info_formatter_types($display['type']);
+      }
+      $display['module'] = $formatter_type_info['module'];
+      // Fill in default settings for the formatter.
+      $display['settings'] += field_info_formatter_settings($display['type']);
+    }
+
+    return $display;
+  }
+
+  /**
+   * Prepares 'extra fields' for the current run-time context.
+   *
+   * @param $extra_fields
+   *   The array of extra fields, as collected in hook_field_extra_fields().
+   * @param $entity_type
+   *   The entity type.
+   * @param $bundle
+   *   The bundle name.
+   *
+   * @return
+   *   The list of extra fields completed for the current runtime context.
+   */
+  public function prepareExtraFields($extra_fields, $entity_type, $bundle) {
+    $entity_type_info = entity_get_info($entity_type);
+    $bundle_settings = field_bundle_settings($entity_type, $bundle);
+    $extra_fields += array('form' => array(), 'display' => array());
+
+    $result = array();
+    // Extra fields in forms.
+    foreach ($extra_fields['form'] as $name => $field_data) {
+      $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array();
+      if (isset($settings['weight'])) {
+        $field_data['weight'] = $settings['weight'];
+      }
+      $result['form'][$name] = $field_data;
+    }
+
+    // Extra fields in displayed entities.
+    $data = $extra_fields['display'];
+    foreach ($extra_fields['display'] as $name => $field_data) {
+      $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array();
+      $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes']));
+      foreach ($view_modes as $view_mode) {
+        if (isset($settings[$view_mode])) {
+          $field_data['display'][$view_mode] = $settings[$view_mode];
+        }
+        else {
+          $field_data['display'][$view_mode] = array(
+            'weight' => $field_data['weight'],
+            'visible' => TRUE,
+          );
+        }
+      }
+      unset($field_data['weight']);
+      $result['display'][$name] = $field_data;
+    }
+
+    return $result;
+  }
+}
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
index 7155af9..df6aa94 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldInfoTest.php
@@ -72,7 +72,7 @@ class FieldInfoTest extends FieldTestBase {
 
     // Verify that no unexpected instances exist.
     $instances = field_info_instances('test_entity');
-    $expected = array('test_bundle' => array());
+    $expected = array();
     $this->assertIdentical($instances, $expected, "field_info_instances('test_entity') returns " . var_export($expected, TRUE) . '.');
     $instances = field_info_instances('test_entity', 'test_bundle');
     $this->assertIdentical($instances, array(), "field_info_instances('test_entity', 'test_bundle') returns an empty array.");
@@ -129,7 +129,7 @@ class FieldInfoTest extends FieldTestBase {
 
     // Test with an entity type that has no bundles.
     $instances = field_info_instances('user');
-    $expected = array('user' => array());
+    $expected = array();
     $this->assertIdentical($instances, $expected, "field_info_instances('user') returns " . var_export($expected, TRUE) . '.');
     $instances = field_info_instances('user', 'user');
     $this->assertIdentical($instances, array(), "field_info_instances('user', 'user') returns an empty array.");
diff --git a/core/modules/field/modules/field_sql_storage/field_sql_storage.module b/core/modules/field/modules/field_sql_storage/field_sql_storage.module
index 69e8bda..9a2f83d 100644
--- a/core/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/core/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -330,11 +330,10 @@ function field_sql_storage_field_storage_delete_field($field) {
  * Implements hook_field_storage_load().
  */
 function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) {
-  $field_info = field_info_field_by_ids();
   $load_current = $age == FIELD_LOAD_CURRENT;
 
   foreach ($fields as $field_id => $ids) {
-    $field = $field_info[$field_id];
+    $field = field_info_field_by_id($field_id);
     $field_name = $field['field_name'];
     $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
 
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index 076cc23..e12b6bf 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -327,23 +327,30 @@ function _field_ui_bundle_admin_path($entity_type, $bundle_name) {
  * Identifies inactive fields within a bundle.
  */
 function field_ui_inactive_instances($entity_type, $bundle_name = NULL) {
-  if (!empty($bundle_name)) {
-    $inactive = array($bundle_name => array());
-    $params = array('bundle' => $bundle_name);
+  $params = array('entity_type' => $entity_type);
+
+  if (empty($bundle_name)) {
+    $active = field_info_instances($entity_type);
+    $inactive = array();
   }
   else {
-    $inactive = array();
-    $params = array();
+    // Restrict to the specified bundle. For consistency with the case where
+    // $bundle_name is NULL, the $active and  $inactive arrays are keyed by
+    // bundle name first.
+    $params['bundle'] = $bundle_name;
+    $active = array($bundle_name => field_info_instances($entity_type, $bundle_name));
+    $inactive = array($bundle_name => array());
   }
-  $params['entity_type'] = $entity_type;
 
-  $active_instances = field_info_instances($entity_type);
+  // Iterate on existing definitions, and spot those that do not appear in the
+  // $active list collected earlier.
   $all_instances = field_read_instances($params, array('include_inactive' => TRUE));
   foreach ($all_instances as $instance) {
-    if (!isset($active_instances[$instance['bundle']][$instance['field_name']])) {
+    if (!isset($active[$instance['bundle']][$instance['field_name']])) {
       $inactive[$instance['bundle']][$instance['field_name']] = $instance;
     }
   }
+
   if (!empty($bundle_name)) {
     return $inactive[$bundle_name];
   }
diff --git a/core/modules/field_ui/lib/Drupal/field_ui/Tests/ManageFieldsTest.php b/core/modules/field_ui/lib/Drupal/field_ui/Tests/ManageFieldsTest.php
index 4aa0dd6..d086cb5 100644
--- a/core/modules/field_ui/lib/Drupal/field_ui/Tests/ManageFieldsTest.php
+++ b/core/modules/field_ui/lib/Drupal/field_ui/Tests/ManageFieldsTest.php
@@ -172,7 +172,7 @@ class ManageFieldsTest extends FieldUiTestBase {
    */
   function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') {
     // Reset the fields info.
-    _field_info_collate_fields_reset();
+    field_info_cache_clear();
     // Assert field settings.
     $field = field_info_field($field_name);
     $this->assertTrue($field['settings']['test_field_setting'] == $string, t('Field settings were found.'));
@@ -262,7 +262,7 @@ class ManageFieldsTest extends FieldUiTestBase {
     $this->fieldUIDeleteField($bundle_path1, $this->field_name, $this->field_label, $this->type);
 
     // Reset the fields info.
-    _field_info_collate_fields_reset();
+    field_info_cache_clear();
     // Check that the field instance was deleted.
     $this->assertNull(field_info_instance('node', $this->field_name, $this->type), t('Field instance was deleted.'));
     // Check that the field was not deleted
@@ -272,7 +272,7 @@ class ManageFieldsTest extends FieldUiTestBase {
     $this->fieldUIDeleteField($bundle_path2, $this->field_name, $this->field_label, $type_name2);
 
     // Reset the fields info.
-    _field_info_collate_fields_reset();
+    field_info_cache_clear();
     // Check that the field instance was deleted.
     $this->assertNull(field_info_instance('node', $this->field_name, $type_name2), t('Field instance was deleted.'));
     // Check that the field was deleted too.
