diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 06f8c68..8022537 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -226,6 +226,169 @@ define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
 define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
 
 /**
+ * Extends ArrayObject to enable it to be used as a caching wrapper.
+ *
+ * This class should be extended by systems that need to cache large amounts
+ * of data and have it represented as an array to calling functions. These
+ * arrays can become very large, so ArrayObject is used to allow different
+ * strategies to be used for caching internally (lazy loading, building caches
+ * over time etc.). This can dramatically reduce the amount of data that needs
+ * to be loaded from cache backends on each request, and memory usage from
+ * static caches of that same data.
+ *
+ * Note that array_* functions do not work with ArrayObject. Systems using
+ * CacheArrayObject should use this only internally. If providing API functions
+ * that return the full array, this can be cached separately or returned
+ * directly. However since CacheArrayObject holds partial content by design, it
+ * should be a normal PHP array or otherwise contain the full structure.
+ *
+ * By default, the class accounts for caches where calling functions might
+ * request keys in the array that won't exist even after a cache rebuild. This
+ * prevents situations where a cache rebuild would be triggered over and over
+ * due to a 'missing' item. These cases are stored internally as a value of
+ * NULL. This means that the offsetGet() and offsetExists() methods
+ * must be overridden if caching an array where the top level values can
+ * legitimately be NULL, and where $object->offsetExists() needs to correctly
+ * return (equivalent to array_key_exists() vs. isset()). This should not
+ * be necessary in the majority of cases.
+ *
+ * Classes extending this class must override at least the
+ * resolveCacheMiss() method to have a working implementation.
+ *
+ * offsetSet() is not overridden by this class by default. In practice this
+ * means that assigning an offset via arrayAccess will only apply while the
+ * object is in scope and will not be written back to the persistent cache.
+ * This follows a similar pattern to static vs. persistent caching in
+ * procedural code. Extending classes may wish to alter this behaviour, for
+ * example by overriding offsetSet() and adding an automatic call to persist().
+ *
+ * @see SchemaCache
+ */
+abstract class CacheArrayObject extends ArrayObject {
+
+  /**
+   * A cid to pass to cache_set() and cache_get().
+   */
+  private $cid;
+
+  /**
+   * A bin to pass to cache_set() and cache_get().
+   */
+  private $bin;
+
+  /**
+   * An array of keys to add to the cache at the end of the request.
+   */
+  protected $keysToSave = array();
+
+  /**
+   * Constructor.
+   *
+   * @param $cid
+   *   The cid for the array being cached.
+   * @param $bin
+   *   The bin to cache the array.
+   */
+  public function __construct($cid, $bin) {
+    $this->cid = $cid;
+    $this->bin = $bin;
+
+    if ($cached = cache_get($this->cid, $this->bin)) {
+      parent::__construct($cached->data);
+    }
+    else {
+      parent::__construct(array());
+    }
+  }
+
+  public function offsetExists($offset) {
+    return $this->offsetGet($offset) !== NULL;
+  }
+
+  public function offsetGet($offset) {
+    if (!parent::offsetExists($offset)) {
+      $this->resolveCacheMiss($offset);
+    }
+    return parent::offsetGet($offset);
+  }
+
+  /**
+   * Flags an offset value to be written to the persistent cache.
+   *
+   * If a value is assigned to a cache object with offsetSet(), by default it
+   * will not be written to the persistent cache unless it is flagged with this
+   * method. This allows items to be cached for the duration of a request,
+   * without necessarily writing back to the persistent cache at the end.
+   *
+   * @param $offset
+   *   The array offset that was request.
+   * @param $persist
+   *   Optional boolean to specify whether the offset should be persisted or
+   *   not, defaults to TRUE. When called with $persist = FALSE the offset will
+   *   be unflagged so that it will not written at the end of the request.
+   */
+  protected function persist($offset, $persist = TRUE) {
+    $this->keysToSave[$offset] = $persist;
+  }
+
+  /**
+   * Resolves a cache miss.
+   *
+   * When an offset is not found in the object, this is treated as a cache
+   * miss. This method allows classes implementing the interface to look up
+   * the actual value and allow it to be cached.
+   *
+   * @param $offset
+   *   The offset that was requested.
+   */
+  abstract protected function resolveCacheMiss($offset);
+
+  /**
+   * Immediately write a value to the persistent cache.
+   *
+   * @param $cid
+   *   The cache ID.
+   * @param $bin
+   *   The cache bin.
+   * @param $data
+   *   The data to write to the persistent cache.
+   * @param $lock
+   *   Whether to acquire a lock before writing to cache.
+   */
+  protected function set($cid, $data, $bin, $lock = TRUE) {
+    $lock_name = $cid . ':' . $bin;
+    // Since this method merges with the existing cache entry if it exists,
+    // ensure that only one process can update the cache item at any one time.
+    // This ensures that different requests can't overwrite each others'
+    // partial version of the cache and should help to avoid stampedes.
+    // When a lock cannot be acquired, the cache will not be written by
+    // that request. To implement locking for cache misses, override
+    // __construct().
+    if (!$lock || lock_acquire($lock_name)) {
+      if ($cached = cache_get($cid, $bin)) {
+        $data = $cached->data + $data;
+      }
+      cache_set($cid, $data, $bin);
+      if ($lock) {
+        lock_release($lock_name);
+      }
+    }
+  }
+
+  public function __destruct() {
+    $data = array();
+    foreach ($this->keysToSave as $offset => $persist) {
+      if ($persist) {
+        $data[$offset] = parent::offsetGet($offset);
+      }
+    }
+    if (!empty($data)) {
+      $this->set($this->cid, $data, $this->bin);
+    }
+  }
+}
+
+/**
  * Start the timer with the specified name. If you start and stop the same
  * timer multiple times, the measured intervals will be accumulated.
  *
@@ -701,6 +864,27 @@ function drupal_get_filename($type, $name, $filename = NULL) {
 }
 
 /**
+ * Extends CacheArrayObject to allow for cumulative caching of variables.
+ */
+class VariableCache extends CacheArrayObject {
+  /**
+   * Redefine persist() as public.
+   *
+   * Allows variable_set() to directly manipulate cache.
+   */
+  public function persist($offset, $persist = TRUE) {
+    parent::persist($offset, $persist);
+  }
+
+  function resolveCacheMiss($offset) {
+    $result = db_query('SELECT value FROM {variable} WHERE name = :name', array(':name' => $offset))->fetchField();
+    $value = $result ? unserialize($result) : NULL;
+    parent::offsetSet($offset, $value);
+    $this->persist($offset);
+  }
+}
+
+/**
  * Load the persistent variable table.
  *
  * The variable table is composed of values that have been saved in the table
@@ -708,28 +892,8 @@ function drupal_get_filename($type, $name, $filename = NULL) {
  * file.
  */
 function variable_initialize($conf = array()) {
-  // NOTE: caching the variables improves performance by 20% when serving
-  // cached pages.
-  if ($cached = cache_get('variables', 'cache_bootstrap')) {
-    $variables = $cached->data;
-  }
-  else {
-    // Cache miss. Avoid a stampede.
-    $name = 'variable_init';
-    if (!lock_acquire($name, 1)) {
-      // Another request is building the variable cache.
-      // Wait, then re-run this function.
-      lock_wait($name);
-      return variable_initialize($conf);
-    }
-    else {
-      // Proceed with variable rebuild.
-      $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed());
-      cache_set('variables', $variables, 'cache_bootstrap');
-      lock_release($name);
-    }
-  }
 
+  $variables = new VariableCache('variables', 'cache_bootstrap');
   foreach ($conf as $name => $value) {
     $variables[$name] = $value;
   }
@@ -781,10 +945,23 @@ function variable_set($name, $value) {
   global $conf;
 
   db_merge('variable')->key(array('name' => $name))->fields(array('value' => serialize($value)))->execute();
-
-  cache_clear_all('variables', 'cache_bootstrap');
-
   $conf[$name] = $value;
+
+  // If the variable cache was initialized, immediately write to cache by 
+  // destroying the object and rebuilding it.
+  if (is_object($conf)) {
+    $conf->persist($name);
+    $temporary = (array) $conf;
+    $conf = new VariableCache('variables', 'cache_bootstrap');
+    foreach ($temporary as $key => $value) {
+      $conf[$key] = $value;
+    }
+  }
+  // variable_set() may be called before variable_initialize(). In that case
+  // just clear the cache.
+  else {
+    cache_clear_all('variables', 'cache_bootstrap');
+  }
 }
 
 /**
@@ -806,9 +983,8 @@ function variable_del($name) {
   db_delete('variable')
     ->condition('name', $name)
     ->execute();
+  $conf[$name] = NULL;
   cache_clear_all('variables', 'cache_bootstrap');
-
-  unset($conf[$name]);
 }
 
 /**
diff --git a/includes/common.inc b/includes/common.inc
index d7189ab..fd229dd 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -2156,7 +2156,7 @@ function url($path = NULL, array $options = array()) {
   $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
 
   // With Clean URLs.
-  if (!empty($GLOBALS['conf']['clean_url'])) {
+  if (variable_get('clean_url', '0')) {
     $path = drupal_encode_path($prefix . $path);
     if ($options['query']) {
       return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment'];
diff --git a/modules/search/search.test b/modules/search/search.test
index 4d37133..38a1680 100644
--- a/modules/search/search.test
+++ b/modules/search/search.test
@@ -1542,7 +1542,7 @@ class SearchConfigSettingsForm extends DrupalWebTestCase {
 /**
  * Tests the search_excerpt() function.
  */
-class SearchExcerptTestCase extends DrupalUnitTestCase {
+class SearchExcerptTestCase extends DrupalWebTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Search excerpt extraction',
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index 40af458..81678a9 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -1238,6 +1238,10 @@ class DrupalWebTestCase extends DrupalTestCase {
       ->condition('test_id', $this->testId)
       ->execute();
 
+    // Reset all statics and variables to perform tests in a clean environment.
+    $conf = array();
+    drupal_static_reset();
+
     // Clone the current connection and replace the current prefix.
     $connection_info = Database::getConnectionInfo('default');
     Database::renameConnection('default', 'simpletest_original_default');
@@ -1280,10 +1284,6 @@ class DrupalWebTestCase extends DrupalTestCase {
     ini_set('log_errors', 1);
     ini_set('error_log', $public_files_directory . '/error.log');
 
-    // Reset all statics and variables to perform tests in a clean environment.
-    $conf = array();
-    drupal_static_reset();
-
     // Set the test information for use in other parts of Drupal.
     $test_info = &$GLOBALS['drupal_test_info'];
     $test_info['test_run_id'] = $this->databasePrefix;
@@ -1345,7 +1345,7 @@ class DrupalWebTestCase extends DrupalTestCase {
     variable_set('site_mail', 'simpletest@example.com');
     variable_set('date_default_timezone', date_default_timezone_get());
     // Set up English language.
-    unset($GLOBALS['conf']['language_default']);
+    $GLOBALS['conf']['language_default'] = NULL;
     $language = language_default();
 
     // Use the test mail class instead of the default mail handler class.
