diff --git a/token.pages.inc b/token.pages.inc
index f4ece50..34f3101 100644
--- a/token.pages.inc
+++ b/token.pages.inc
@@ -14,12 +14,14 @@ function theme_tree_table($variables) {
   foreach ($variables['rows'] as &$row) {
     $row += array('class' => array());
     if (!empty($row['parent'])) {
-      $row['class'][] = 'child-of-' . drupal_clean_css_identifier($row['parent']);
+      // $row['parent'] is already a valid class name here, so no need to call
+      // drupal_clean_css_identifier()
+      $row['class'][] = 'child-of-' . $row['parent'];
       unset($row['parent']);
     }
   }
 
-  if (count($variables['rows'])) {
+  if (!empty($variables['rows'])) {
     drupal_add_library('token', 'treeTable');
   }
 
@@ -32,8 +34,11 @@ function theme_tree_table($variables) {
  * @ingroup themeable
  */
 function theme_token_tree($variables) {
+  $trees = &drupal_static(__FUNCTION__, array());
   $token_types = $variables['token_types'];
   $info = token_get_info();
+  drupal_add_js(drupal_get_path('module', 'token') . '/token.js');
+  drupal_add_css(drupal_get_path('module', 'token') . '/token.css');
 
   if ($token_types == 'all') {
     $token_types = array_keys($info['types']);
@@ -41,75 +46,106 @@ function theme_token_tree($variables) {
   elseif ($variables['global_types']) {
     $token_types = array_merge($token_types, token_get_global_token_types());
   }
+  $multiple_token_types = count($token_types) > 1;
 
-  $header = array(
-    t('Name'),
-    t('Token'),
-    t('Description'),
+  $options = array(
+    'flat' => TRUE,
+    'restricted' => $variables['show_restricted'],
+    'depth' => $variables['recursion_limit'],
   );
-  $rows = array();
 
-  foreach ($info['types'] as $type => $type_info) {
-    if (!in_array($type, $token_types)) {
-      continue;
-    }
-
-    if (count($token_types) > 1) {
-      $row = _token_token_tree_format_row($type, $type_info, TRUE);
-      unset($row['data']['value']);
-      $rows[] = $row;
-    }
+  $cid = 'theme_token_tree' . hash('sha256', serialize(array_merge($token_types, $options)));
+  if (isset($trees[$cid])) {
+    return $trees[$cid];
+  }
 
-    $options = array(
-      'flat' => TRUE,
-      'restricted' => $variables['show_restricted'],
-      'depth' => $variables['recursion_limit'],
+  if ($cached = cache_get($cid, 'cache_token')) {
+    // Add the tree table library to ensure that the table is rendered
+    // correctly if theme_tree_table() does not run.
+    drupal_add_library('token', 'treeTable');
+    $return = $trees[$cid] = $cached->data;
+  }
+  else {
+    $header = array(
+      t('Name'),
+      t('Token'),
+      t('Description'),
     );
-    $tree = token_build_tree($type, $options);
-    foreach ($tree as $token => $token_info) {
-      if (!empty($token_info['restricted']) && empty($variables['show_restricted'])) {
+    $rows = array();
+
+    foreach ($info['types'] as $type => $type_info) {
+      if (!in_array($type, $token_types)) {
         continue;
       }
-      if (count($token_types) > 1 && !isset($token_info['parent'])) {
-        $token_info['parent'] = $type;
+
+      if ($multiple_token_types) {
+        $row = _token_token_tree_format_row($type, $type_info, TRUE);
+        unset($row['data']['value']);
+        $rows[] = $row;
+      }
+
+      $tree = token_build_tree($type, $options);
+      foreach ($tree as $token => $token_info) {
+        if (!empty($token_info['restricted']) && empty($variables['show_restricted'])) {
+          continue;
+        }
+        if ($multiple_token_types && !isset($token_info['parent'])) {
+          $token_info['parent'] = $type;
+        }
+        $row = _token_token_tree_format_row($token, $token_info);
+        unset($row['data']['value']);
+        $rows[] = $row;
       }
-      $row = _token_token_tree_format_row($token, $token_info);
-      unset($row['data']['value']);
-      $rows[] = $row;
     }
-  }
 
-  drupal_add_js(drupal_get_path('module', 'token') . '/token.js');
-  drupal_add_css(drupal_get_path('module', 'token') . '/token.css');
 
-  $table_options = array(
-    'header' => $header,
-    'rows' => $rows,
-    'attributes' => array('class' => array('token-tree')),
-    'empty' => t('No tokens available.'),
-  );
-  if ($variables['click_insert']) {
-    $table_options['caption'] = t('Click a token to insert it into the field you\'ve last clicked.');
-    $table_options['attributes']['class'][] = 'token-click-insert';
+    $table_options = array(
+      'header' => $header,
+      'rows' => $rows,
+      'attributes' => array('class' => array('token-tree')),
+      'empty' => t('No tokens available.'),
+    );
+    if ($variables['click_insert']) {
+      $table_options['caption'] = t('Click a token to insert it into the field you\'ve last clicked.');
+      $table_options['attributes']['class'][] = 'token-click-insert';
+    }
+
+    $trees[$cid] = $return = theme('tree_table', $table_options);
+    cache_set($cid, $return, 'cache_token');
   }
 
-  return theme('tree_table', $table_options);
+  return $return;
 }
 
 /**
  * Build a row in the token tree.
  */
 function _token_token_tree_format_row($token, array $token_info, $is_group = FALSE) {
-  $row = array(
-    'id' => _token_clean_css_identifier($token),
+  static $replacements;
+  static $parents;
+
+  if (!isset($replacements)) {
+    $replacements = _token_css_identifier_replacements();
+  }
+
+  // Build a statically cached array of default values. This is around four
+  // times as efficient as building the base array from scratch each time this
+  // function is called.
+  static $defaults = array(
+    'id' => '',
     'class' => array(),
     'data' => array(
-      'name' => $token_info['name'],
-      'token' => '',
+      'name' => '',
+      'token' => array(),
+      'class' => array(),
       'value' => '',
-      'description' => $token_info['description'],
+      'description' => '',
     ),
   );
+  $row = $defaults;
+  $row['id'] = 'token-' . strtr(trim($token, '[]'), $replacements);
+  $row['data']['name'] = $token_info['name'];
+  $row['data']['description'] = $token_info['description'];
 
   if ($is_group) {
     // This is a token type/group.
@@ -117,15 +153,18 @@ function _token_token_tree_format_row($token, array $token_info, $is_group = FAL
   }
   else {
     // This is a token.
-    $row['data']['token'] = array(
-      'data' => $token,
-      'class' => array('token-key'),
-    );
+    $row['data']['token']['data'] = $token;
+    $row['data']['token']['class'][] = 'token-key';
     if (isset($token_info['value'])) {
       $row['data']['value'] = $token_info['value'];
     }
     if (!empty($token_info['parent'])) {
-      $row['parent'] = _token_clean_css_identifier($token_info['parent']);
+      if (isset($parents[$token_info['parent']])) {
+        $row['parent'] = $parents[$token_info['parent']];
+      }
+      else {
+        $row['parent'] = $parents[$token_info['parent']] = 'token-' . strtr(trim($token_info['parent'], '[]'), $replacements);
+      }
     }
   }
 
@@ -141,7 +180,15 @@ function _token_token_tree_format_row($token, array $token_info, $is_group = FAL
  * @see drupal_clean_css_identifier().
  */
 function _token_clean_css_identifier($id) {
-  return rtrim(drupal_clean_css_identifier('token-' . trim($id, '[]'), array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '', ':' => '-')), '-');
+  static $replacements;
+  if (!isset($replacements)) {
+    $replacements = _token_css_identifier_replacements();
+  }
+  return 'token-' . strtr(trim($id, '[]'), $replacements);
+}
+
+function _token_css_identifier_replacements() {
+  return array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '', ':' => '-');
 }
 
 /**
