Change record status: 
Project: 
Introduced in branch: 
7.x
Introduced in version: 
7.22
Description: 

The way Field API caches the metadata about existing fields and field instances has been vastly optimized in Drupal release 7.22, resulting in potentially important performance gains on sites with a moderate-to-large number of fields and entity types (up to -20% CPU time & -40% memory use in "pure" cases).

The changes are strictly backwards compatible and introduce no API or functional break, but depending on the collection of contributed or custom modules used on a given site, some code changes might be needed for the performance gains to be fully leveraged. Contributed modules authors are therefore highly encouraged to check the code of their modules and release new versions if needed. More information and code examples below.

Short version:

  • Field and instance definitions are now cached by entity bundle, and can thus be loaded in memory only for the bundles involved in the current request.
  • Calls to the following functions annihilate the memory optimizations for the rest of the request, and should thus be avoided whenever possible in "regular" page requests:
    - field_info_fields(),
    - field_info_field_by_ids(),
    - field_info_instances() without $entity_type and $bundle parameters,
    (i.e. "get the definitions all fields / field instances that exist on the site")
  • To iterate on fields and instances across bundles, use the newly added field_info_field_map() when possible.
  • Never call _field_info_collate_fields() directly.

Change summary

Before the 7.22 release, core's Field API cached information on all field and field instance definitions across all entity types and bundles in one single cache entry, loaded and kept in memory on each request. On sites with lots of entity types and fields, the size of this entry could reach several megabytes, causing memory bloat and CPU unserialization costs.

This information is now collected and cached separately for each entity bundle (e.g. each node type), allowing to load in memory only the field and instance definitions relevant for the bundles involved in the current request.

The field_info_*() functions are optimized for the most frequent use case of: "do something for all the fields present on an entity". More precisely :

Code samples

Iterate on all fields of an entity or a bundle

A. The typical code pattern for this in earlier Drupal 7 releases was :

// Get all field definitions in advance to save individual function calls to field_info_field().
$fields = field_info_fields();
foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
  $field = $fields[$field_name];
  // Do something with the $field and $instance
}

B. The memory-efficient way after Drupal 7.22 :

foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
  $field = field_info_field($field_name);
  // Do something with the $field and $instance
}

Snippets A and B both work whatever the Drupal 7 version, but B will consume much less memory on 7.22+, and will be only insignificantly slower in previous core releases.
B should therefore be preferred for both custom code and contributed modules, without compatibility concerns.

Iterate on all instances of a text field across all entity types and bundles

A. The typical code pattern for this in earlier Drupal 7 releases was :

$fields = field_info_fields();
foreach (field_info_instances() as $field_name => $instance) {
  $field = $fields[$field_name];
  if ($field['type'] == 'text') {
    // Do something with the $field and $instance
  }
}

B. The memory-efficient way after Drupal 7.22 :

foreach (field_info_field_map() as $field_name => $data) {
  if ($data['type'] == 'text') {
    $field = field_info_field($field_name);
    foreach ($data['bundles'] as $entity_type => $bundles) {
      foreach ($bundles as $bundle) {
        $instance = field_info_instance($entity_type, $field_name, $bundle);
        // Do something with the $field and $instance
      }
    }
  }
}

Remarks:

  • field_info_field_map() was added in Drupal 7.22, so contributed D7 modules should only use it within a check on the VERSION constant.
    if (VERSION >= '7.22') {
      // snippet B.
    }
    else {
      // snippet A.
    }
    
  • There are still cases where iterating on field_info_instances() is needed (e.g. Views module assembling metadata about all existing fields). In such cases, try to make sure the computation happens below some cache point and not during a regular page request. Alternatively, consider computing and caching the data by entity type and bundle separately.

Recap

Cheap:
- field_info_field_map() (added in Drupal 7.22)
- field_info_instances($entity_type, $bundle)
- field_info_field($field_name) & field_info_field_by_id($field_id), if the field appears in a bundle on which field_info_instances($entity_type, $bundle) has already been called - otherwise it's O(n) on the number of different fields loaded.
- field_info_instance($entity_type, $field_name, $bundle) - same as above, cheap if comes after field_info_instances($entity_type, $bundle), O(n) if not.

Costly:
- field_info_fields(), field_info_field_by_ids() - costly, loads all field definitions
- field_info_instances(), field_info_instances($entity_type) - extra costly, loads all field and instance definitions.

Impacts: 
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done