diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 112e4f5..2b3fec1 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -910,6 +910,8 @@ function _menu_link_translate(&$item, $translate = FALSE) {
     drupal_alter('translated_menu_link', $item, $map);
   }
 
+  $item['rendered'] = l($item['title'], $item['href'], $item['localized_options']);
+
   return $map;
 }
 
@@ -963,6 +965,17 @@ function menu_get_object($type = 'node', $position = 1, $path = NULL) {
 function menu_tree($menu_name) {
   $menu_output = &drupal_static(__FUNCTION__, array());
 
+  if (substr($menu_name, 0, 5) == 'devel' && !isset($menu_output[$menu_name])) {
+    $t0 = microtime(TRUE);
+    $tree = menu_tree_page_data($menu_name);
+    $t1 = microtime(TRUE);
+    $menu_output[$menu_name] = menu_tree_output($tree);
+    $t2 = microtime(TRUE);
+    $output = drupal_render($menu_output[$menu_name]);
+    $t3 = microtime(TRUE);
+    return array('#markup' => '<div>' . implode('<br/>', array(1000*($t1-$t0), 1000*($t2-$t1), 1000*($t3-$t2))) . '</div>' . $output);
+  }
+
   if (!isset($menu_output[$menu_name])) {
     $tree = menu_tree_page_data($menu_name);
     $menu_output[$menu_name] = menu_tree_output($tree);
@@ -1038,6 +1051,7 @@ function menu_tree_output($tree) {
     $element['#href'] = $data['link']['href'];
     $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
     $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below'];
+    $element['#rendered_link'] = $data['link']['rendered'];
     $element['#original_link'] = $data['link'];
     // Index using the link's unique mlid.
     $build[$data['link']['mlid']] = $element;
@@ -1323,11 +1337,54 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
  *   A fully built menu tree.
  */
 function menu_build_tree($menu_name, array $parameters = array()) {
-  // Build the menu tree.
-  $data = _menu_build_tree($menu_name, $parameters);
-  // Check access for the current user to each item in the tree.
-  menu_tree_check_access($data['tree'], $data['node_links']);
-  return $data['tree'];
+  global $user;
+  $language_interface = language(LANGUAGE_TYPE_INTERFACE);
+  $trees = &drupal_static(__FUNCTION__, array());
+
+  $subtrees = array();
+  $subtree_parameters = array();
+  if (empty($parameters['expanded'])) {
+    $subtree_parameters[] = array('active_trail' => array(), 'only_active_trail' => FALSE,) + $parameters;
+  }
+  else {
+    foreach ($parameters['expanded'] as $plid) {
+      $subtree_parameters[$plid] = array('expanded' => array($plid), 'active_trail' => array(), 'only_active_trail' => FALSE,) + $parameters;
+    }
+  }
+
+  foreach ($subtree_parameters as $plid => $parameters) {
+    $cid = 'links:' . $menu_name . ':tree-data-localized:' . $user->uid . ':' . $language_interface->langcode . ':' . hash('sha256', serialize($parameters));
+
+    // If we do not have this tree in the static cache, check {cache_menu}.
+    if (!isset($trees[$cid])) {
+      $cache = cache('menu')->get($cid);
+      if ($cache && isset($cache->data)) {
+        $trees[$cid] = $cache->data;
+      }
+    }
+
+    if (!isset($trees[$cid])) {
+      $data = _menu_build_tree($menu_name, $parameters);
+      menu_tree_check_access($data['tree'], $data['node_links']);
+      $trees[$cid] = $data['tree'];
+      cache('menu')->set($cid, $trees[$cid], CacheBackendInterface::CACHE_PERMANENT, array('menu' => $menu_name));
+    }
+
+    $subtrees[$plid] = $trees[$cid];
+  }
+
+  // Reconstruct a complete tree from the subtrees.
+  $tree = array();
+  $parents = array();
+  foreach ($subtrees as $plid => $subtree) {
+    $subtree_parents = isset($parents[$plid]) ? $parents[$plid] : array();
+    foreach ($subtree as $key => $item) {
+      drupal_array_set_nested_value($tree, array_merge($subtree_parents, array($key)), $item);
+      $parents[$item['link']['mlid']] = array_merge($subtree_parents, array($key, 'below'));
+    }
+  }
+
+  return $tree;
 }
 
 /**
@@ -1412,8 +1469,12 @@ function _menu_build_tree($menu_name, array $parameters = array()) {
 
     // Build an ordered array of links using the query result object.
     $links = array();
+    $min_depth = 0;
     foreach ($query->execute() as $item) {
       $links[] = $item;
+      if (!$min_depth || ($item['depth'] < $min_depth)) {
+        $min_depth = $item['depth'];
+      }
     }
     $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
     $data['tree'] = menu_tree_data($links, $active_trail, $min_depth);
@@ -1605,7 +1666,7 @@ function theme_menu_link(array $variables) {
   if ($element['#below']) {
     $sub_menu = drupal_render($element['#below']);
   }
-  $output = l($element['#title'], $element['#href'], $element['#localized_options']);
+  $output = $element['#rendered_link'];
   return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
 }
 
