diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php
index 329cf16..940d605 100644
--- a/core/modules/field/field.api.php
+++ b/core/modules/field/field.api.php
@@ -1647,11 +1647,14 @@ 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];
+    // By the time hook_field_storage_load() runs, the relevant fields have
+    // been populated in the static cache, 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().
+    $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 9291725..3875eb2 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -281,7 +281,6 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b =
     'language' => NULL,
   );
   $options += $default_options;
-  $field_info = field_info_field_by_ids();
 
   $fields = array();
   $grouped_instances = array();
@@ -305,7 +304,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.
@@ -612,7 +611,6 @@ function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcod
  *     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.
@@ -690,7 +688,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 6df3235..7eab359 100644
--- a/core/modules/field/field.crud.inc
+++ b/core/modules/field/field.crud.inc
@@ -319,7 +319,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
@@ -337,8 +341,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 5a99034..e24eb0e 100644
--- a/core/modules/field/field.info.inc
+++ b/core/modules/field/field.info.inc
@@ -6,6 +6,730 @@
  */
 
 /**
+ * Provides field and instance definitions for the current runtime environment.
+ *
+ * This class holds static and persistent caches of the collected data. The
+ * methods retrieving 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.
+ *
+ * The persistent cache uses one cache entry per bundle, storing both fields and
+ * instances. Fields used in multiple bundles are thus replicated in several
+ * cache entries (the static cache). Cache entries are loaded for bundles as a
+ * whole, optimizing for the most common pattern of iterating over the instances
+ * of a bundle (and then probably accessing each corresponding field) rather
+ * than accessing a single one. The specific caching strategy and
+ * interdependencies between fields and instances make the DrupalCacheArray
+ * class unfit here.
+ */
+class FieldInfo {
+
+  /**
+   * Flag indicating a read or write operation on the static cache.
+   */
+  const CACHE_STATIC = 'static';
+
+    /**
+   * Flag indicating a read or write operation on the persistent cache.
+   */
+  const CACHE_PERISTENT = 'persistent';
+
+  /**
+   * 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;
+
+  /**
+   * Mapping of field names to the ID of the corresponding non-deleted field.
+   *
+   * @var array
+   */
+  protected $fieldIdsByName;
+
+  /**
+   * Contents of bundles ($instance definitions and extra fields).
+   *
+   * @var array
+   */
+  protected $bundleInfo;
+
+  /**
+   * Constructs a FieldInfo object.
+   */
+  function __construct() {
+    $this->init();
+  }
+
+  /**
+   * Clears the static and persistent caches.
+   */
+  public function flush() {
+    $this->init();
+    cache('field')->deletePrefix('info_fields:');
+  }
+
+  /**
+   * Initializes the static cache.
+   */
+  protected function init() {
+    $this->fieldMap = array();
+    $this->fieldsById = array();
+    $this->fieldIdsByName = array();
+    $this->bundleInfo = array();
+  }
+
+  /**
+   * Retrieves the field map from cache.
+   *
+   * @param $cache
+   *   The type of cache. Either self::CACHE_STATIC or self::CACHE_PERISTENT.
+   *
+   * @return
+   *   The field map, as returned by getFieldMap(), if present in the specified
+   *   cache, else NULL.
+   */
+  protected function cacheGetFieldMap($cache) {
+    switch ($cache) {
+      case self::CACHE_STATIC:
+        if ($this->fieldMap) {
+          return $this->fieldMap;
+        }
+        break;
+
+      case self::CACHE_PERISTENT:
+        if ($cached = cache('field')->get('field_map')) {
+          return $cached->data;
+        }
+        break;
+    }
+  }
+
+  /**
+   * Stores the field map in cache.
+   *
+   * @param $cache
+   *   The type of cache. Either self::CACHE_STATIC or self::CACHE_PERISTENT.
+   * @param $map
+   *   The field map, as returned by getFieldMap().
+   */
+  protected function cacheSetFieldMap($cache, $map) {
+    switch ($cache) {
+      case self::CACHE_STATIC:
+        $this->fieldMap = $map;
+        break;
+
+      case self::CACHE_PERISTENT:
+        cache('field')->set('field_map', $map);
+        break;
+    }
+  }
+
+  /**
+   * Retrieves a field definition from cache.
+   *
+   * @param $cache
+   *   This parameter is only present for consistency and is not actually used.
+   *   Only self::CACHE_STATIC is implemented, fields only enter the persistent
+   *   cache as part of a full bundle.
+   * @param $field_name
+   *   The field name.
+   *
+   * @return
+   *   The field definition, if present in the static cache, else NULL.
+   */
+  protected function cacheGetField($cache, $field_name) {
+    if (isset($this->fieldIdsByName[$field_name])) {
+      return $this->fieldsById[$this->fieldIdsByName[$field_name]];
+    }
+  }
+
+  /**
+   * Retrieves a field definition from the static cache
+   *
+   * @param $cache
+   *   This parameter is only present for consistency and is not actually used.
+   *   Only self::CACHE_STATIC is implemented, fields only enter the persistent
+   *   cache as part of a full bundle.
+   * @param $field_id
+   *   The field ID.
+   *
+   * @return
+   *   The field definition, if present in the static cache, else NULL.
+   */
+  protected function cacheGetFieldbyId($cache, $field_id) {
+    if (isset($this->fieldsById[$field_id])) {
+      return $this->fieldsById[$field_id];
+    }
+  }
+
+  /**
+   * Stores a field definition in the static cache.
+   *
+   * @param $cache
+   *   This parameter is only present for consistency and is not actually used.
+   *   Only self::CACHE_STATIC is implemented, fields only enter the persistent
+   *   cache as part of a full bundle.
+   * @param $field
+   *   The field definition.
+   */
+  protected function cacheSetField($cache, $field) {
+    $this->fieldsById[$field['id']] = $field;
+    // Allow the retrieval of non-deleted fields by their name.
+    if (!$field['deleted']) {
+      $this->fieldIdsByName[$field['field_name']] = $field['id'];
+    }
+  }
+
+  /**
+   * Retrieves the contents of a bundle from the static cache.
+   *
+   * @param $cache
+   *   The type of cache. Either self::CACHE_STATIC or self::CACHE_PERISTENT.
+   * @param $entity_type
+   *   The entity type.
+   * @param $bundle
+   *   The bundle name.
+   *
+   * @return
+   *   The contents of the bundle, as returned by getBundleInfo(), if present in
+   *   the static cache, else NULL.
+   */
+  protected function cacheGetBundleInfo($cache, $entity_type, $bundle) {
+    switch ($cache) {
+      case self::CACHE_STATIC:
+        if (isset($this->bundleInfo[$entity_type][$bundle])) {
+          return $this->bundleInfo[$entity_type][$bundle];
+        }
+        break;
+
+      case self::CACHE_PERISTENT:
+        if ($cached = cache('field')->get("bundle_info:$entity_type:$bundle")) {
+          return $cached->data;
+        }
+        break;
+    }
+  }
+
+  /**
+   * Stores the contents of a bundle in the static cache.
+   *
+   * @param $cache
+   *   The type of cache. Either self::CACHE_STATIC or self::CACHE_PERISTENT.
+   * @param $entity_type
+   *   The entity type.
+   * @param $bundle
+   *   The bundle name.
+   * @param $info
+   *   The contents of the bundle, as returned by getBundleInfo()
+   */
+  protected function cacheSetBundleInfo($cache, $entity_type, $bundle, $info) {
+    switch ($cache) {
+      case self::CACHE_STATIC:
+        $this->bundleInfo[$entity_type][$bundle] = $info;
+        break;
+
+      case self::CACHE_PERISTENT:
+        cache('field')->set("bundle_info:$entity_type:$bundle", $info);
+        break;
+    }
+  }
+
+  /**
+   * 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() {
+    if ($map = $this->cacheGetFieldMap(self::CACHE_STATIC)) {
+      return $map;
+    }
+
+    if ($map = $this->cacheGetFieldMap(self::CACHE_PERSISTENT)) {
+      $this->sacheSetFieldMap(self::CACHE_STATIC, $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;
+    }
+
+    $this->cacheSetFieldMap(self::CACHE_STATIC, $map);
+    $this->cacheSetFieldMap(self::CACHE_PERISTENT, $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;
+  }
+
+  /**
+   * 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) {
+    if ($field = $this->cacheGetField(self::CACHE_STATIC, $field_name)) {
+      return $field;
+    }
+
+    if ($field = field_read_field(array('field_name' => $field_name))) {
+      $field = $this->prepareField($field);
+
+      // Save in the static cache (fields only enter the persistent cache as
+      // part of a full bundle).
+      $this->cachesetField(self::CACHE_STATIC, $field);
+
+      return $field;
+    }
+  }
+
+  /**
+   * 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) {
+    if ($field = $this->cacheGetFieldById(self::CACHE_STATIC, $field_id)) {
+      return $field;
+    }
+
+    if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) {
+      $field = current($fields);
+      $field = $this->prepareField($field);
+
+      // Save in the static cache (fields only enter the persistent cache as
+      // part of a full bundle).
+      $this->cachesetField(self::CACHE_STATIC, $field);
+
+      return $field;
+    }
+  }
+
+  /**
+   * 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;
+    }
+  }
+
+  /**
+   * Retrieves the instances and extra fields for a bundle.
+   *
+   * The function also populates the correpsonding 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) {
+    if ($info = $this->cacheGetBundleInfo(self::CACHE_STATIC, $entity_type, $bundle)) {
+      return $info;
+    }
+
+    if ($info = $this->cacheGetBundleInfo(self::CACHE_PERISTENT, $entity_type, $bundle)) {
+      // Cache hit: Extract the field definitions and store them in memory.
+      foreach ($info['fields'] as $field) {
+        $this->cacheSetField(self::CACHE_STATIC, $field);
+      }
+      unset($info['fields']);
+
+      $this->cacheSetBundleInfo(self::CACHE_STATIC, $entity_type, $bundle, $info);
+      return $info;
+    }
+
+    // Cache miss: collect the information from the database.
+
+    $info = array(
+      'instances' => array(),
+      'extra_fields' => array(),
+    );
+
+    // Pre-fetch the fields in the bundle.
+    $params = array('entity_type' => $entity_type, 'bundle' => $bundle);
+    $fields = field_read_fields($params, array('include_deleted' => 1));
+
+    // 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_id']];
+
+      $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 (!$this->cacheGetFieldById(self::CACHE_STATIC, $field['id'])) {
+        $field = $this->prepareField($field);
+        $this->cacheSetField(self::CACHE_STATIC, $field);
+      }
+    }
+
+    // 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);
+    }
+
+    // The method might get called on invalid entity types or bundles, for
+    // which we do not want to pollute our caches. However, there might be
+    // edge cases where the entity type and bundle are valid but still unknown
+    // by a stale entity_get_info(). Thus on unknown entity types or bundles,
+    // we do check the corresponding field instances, but do not cache the
+    // result.
+    $entity_info = entity_get_info($entity_type);
+    if (isset($entity_info['bundles'][$bundle])) {
+      $this->cacheSetBundleInfo(self::CACHE_STATIC, $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->cacheGetFieldbyId(self::CACHE_STATIC, $instance['field_id']);
+      }
+      $this->cacheSetBundleInfo(self::CACHE_PERISTENT, $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, $instance);
+    $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;
+  }
+}
+
+/**
+ * Retrieves the FieldInfo object for the current request.
+ *
+ * @return
+ *   An instance of the 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;
+}
+
+/**
+ * Resets the cached data about existing fields and instances.
+ */
+function _field_info_field_reset() {
+  $cache = _field_info_field_cache();
+  $cache->flush();
+}
+
+/**
  * @defgroup field_info Field Info API
  * @{
  * Obtain information about Field API configuration.
@@ -34,7 +758,7 @@ function field_info_cache_clear() {
   entity_info_cache_clear();
 
   _field_info_collate_types_reset();
-  _field_info_collate_fields_reset();
+  _field_info_field_reset();
 }
 
 /**
@@ -167,289 +891,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 +1037,19 @@ function field_info_bundles($entity_type = NULL) {
 /**
  * Returns all field definitions.
  *
+ * Warning: 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();
 }
 
 /**
@@ -628,10 +1068,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);
 }
 
 /**
@@ -649,17 +1087,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().
+ * Warning: 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
@@ -670,17 +1108,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
@@ -689,22 +1133,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)) {
-    return $info['instances'];
+    return $cache->getInstances();
   }
   if (!isset($bundle_name)) {
-    return $info['instances'][$entity_type];
+    return $cache->getInstances($entity_type);
   }
-  if (isset($info['instances'][$entity_type][$bundle_name])) {
-    return $info['instances'][$entity_type][$bundle_name];
-  }
-  return array();
+
+  $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
@@ -713,9 +1161,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];
   }
 }
 
@@ -773,9 +1222,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 2d31efa..485b3f6 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -892,7 +892,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/modules/field_sql_storage/field_sql_storage.module b/core/modules/field/modules/field_sql_storage/field_sql_storage.module
index 92d244a..097c625 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
@@ -324,11 +324,14 @@ 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];
+    // By the time hook_field_storage_load() runs, the relevant fields have
+    // been populated in the static cache, 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().
+    $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 ab807dc..a00cf7c 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -344,7 +344,10 @@ function field_ui_inactive_instances($entity_type, $bundle_name = NULL) {
   }
   $params['entity_type'] = $entity_type;
 
-  $active_instances = field_info_instances($entity_type);
+  $active_instances = field_info_instances($entity_type, $bundle_name);
+  if (!empty($bundle_name)) {
+    $active_instances = array($bundle_name => $active_instances);
+  }
   $all_instances = field_read_instances($params, array('include_inactive' => TRUE));
   foreach ($all_instances as $instance) {
     if (!isset($active_instances[$instance['bundle']][$instance['field_name']])) {
diff --git a/core/modules/field_ui/field_ui.test b/core/modules/field_ui/field_ui.test
index adfd900..4b3babc 100644
--- a/core/modules/field_ui/field_ui.test
+++ b/core/modules/field_ui/field_ui.test
@@ -298,7 +298,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
    */
   function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') {
     // Reset the fields info.
-    _field_info_collate_fields_reset();
+    _field_info_field_reset();
     // Assert field settings.
     $field = field_info_field($field_name);
     $this->assertTrue($field['settings']['test_field_setting'] == $string, t('Field settings were found.'));
@@ -389,7 +389,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
     $this->fieldUIDeleteField($bundle_path1, $this->field_name, $this->field_label, $this->type);
 
     // Reset the fields info.
-    _field_info_collate_fields_reset();
+    _field_info_field_reset();
     // 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
@@ -399,7 +399,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
     $this->fieldUIDeleteField($bundle_path2, $this->field_name, $this->field_label, $type_name2);
 
     // Reset the fields info.
-    _field_info_collate_fields_reset();
+    _field_info_field_reset();
     // 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.
