Changing the aging mechanism for script and eval caches.
authorverwaest@chromium.org <verwaest@chromium.org>
Fri, 31 Oct 2014 14:51:48 +0000 (14:51 +0000)
committerverwaest@chromium.org <verwaest@chromium.org>
Fri, 31 Oct 2014 14:52:27 +0000 (14:52 +0000)
Instead of using multiple generations for the code, first only store the hash that gets aged. Once a hash matched on a next probe, actually cache the code. Use regular code aging to remove entries from the cache.

BUG=
R=ulan@chromium.org

Review URL: https://codereview.chromium.org/675013004

Cr-Commit-Position: refs/heads/master@{#25040}
git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@25040 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/compilation-cache.cc
src/compilation-cache.h
src/objects.cc
src/objects.h
test/cctest/test-heap.cc

index aab2fe5..6c9f95a 100644 (file)
@@ -13,11 +13,6 @@ namespace internal {
 
 
 // The number of generations for each sub cache.
-// The number of ScriptGenerations is carefully chosen based on histograms.
-// See issue 458: http://code.google.com/p/v8/issues/detail?id=458
-static const int kScriptGenerations = 5;
-static const int kEvalGlobalGenerations = 2;
-static const int kEvalContextualGenerations = 2;
 static const int kRegExpGenerations = 2;
 
 // Initial size of each compilation cache table allocated.
@@ -26,9 +21,9 @@ static const int kInitialCacheSize = 64;
 
 CompilationCache::CompilationCache(Isolate* isolate)
     : isolate_(isolate),
-      script_(isolate, kScriptGenerations),
-      eval_global_(isolate, kEvalGlobalGenerations),
-      eval_contextual_(isolate, kEvalContextualGenerations),
+      script_(isolate, 1),
+      eval_global_(isolate, 1),
+      eval_contextual_(isolate, 1),
       reg_exp_(isolate, kRegExpGenerations),
       enabled_(true) {
   CompilationSubCache* subcaches[kSubCacheCount] =
@@ -58,6 +53,14 @@ Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) {
 
 
 void CompilationSubCache::Age() {
+  // Don't directly age single-generation caches.
+  if (generations_ == 1) {
+    if (tables_[0] != isolate()->heap()->undefined_value()) {
+      CompilationCacheTable::cast(tables_[0])->Age();
+    }
+    return;
+  }
+
   // Age the generations implicitly killing off the oldest.
   for (int i = generations_ - 1; i > 0; i--) {
     tables_[i] = tables_[i - 1];
@@ -102,9 +105,7 @@ void CompilationSubCache::Remove(Handle<SharedFunctionInfo> function_info) {
 
 CompilationCacheScript::CompilationCacheScript(Isolate* isolate,
                                                int generations)
-    : CompilationSubCache(isolate, generations),
-      script_histogram_(NULL),
-      script_histogram_initialized_(false) { }
+    : CompilationSubCache(isolate, generations) {}
 
 
 // We only re-use a cached function for some script source code if the
@@ -173,20 +174,6 @@ Handle<SharedFunctionInfo> CompilationCacheScript::Lookup(
     }
   }
 
-  if (!script_histogram_initialized_) {
-    script_histogram_ = isolate()->stats_table()->CreateHistogram(
-        "V8.ScriptCache",
-        0,
-        kScriptGenerations,
-        kScriptGenerations + 1);
-    script_histogram_initialized_ = true;
-  }
-
-  if (script_histogram_ != NULL) {
-    // The level NUMBER_OF_SCRIPT_GENERATIONS is equivalent to a cache miss.
-    isolate()->stats_table()->AddHistogramSample(script_histogram_, generation);
-  }
-
   // Once outside the manacles of the handle scope, we need to recheck
   // to see if we actually found a cached script. If so, we return a
   // handle created in the caller's handle scope.
index 6799b1c..a7c84b7 100644 (file)
@@ -89,9 +89,6 @@ class CompilationCacheScript : public CompilationSubCache {
                  int column_offset,
                  bool is_shared_cross_origin);
 
-  void* script_histogram_;
-  bool script_histogram_initialized_;
-
   DISALLOW_IMPLICIT_CONSTRUCTORS(CompilationCacheScript);
 };
 
index ea07582..7cc9266 100644 (file)
@@ -13643,7 +13643,11 @@ class StringSharedKey : public HashTableKey {
 
   bool IsMatch(Object* other) OVERRIDE {
     DisallowHeapAllocation no_allocation;
-    if (!other->IsFixedArray()) return false;
+    if (!other->IsFixedArray()) {
+      if (!other->IsNumber()) return false;
+      uint32_t other_hash = static_cast<uint32_t>(other->Number());
+      return Hash() == other_hash;
+    }
     FixedArray* other_array = FixedArray::cast(other);
     SharedFunctionInfo* shared = SharedFunctionInfo::cast(other_array->get(0));
     if (shared != *shared_) return false;
@@ -13683,6 +13687,9 @@ class StringSharedKey : public HashTableKey {
 
   uint32_t HashForObject(Object* obj) OVERRIDE {
     DisallowHeapAllocation no_allocation;
+    if (obj->IsNumber()) {
+      return static_cast<uint32_t>(obj->Number());
+    }
     FixedArray* other_array = FixedArray::cast(obj);
     SharedFunctionInfo* shared = SharedFunctionInfo::cast(other_array->get(0));
     String* source = String::cast(other_array->get(1));
@@ -14841,7 +14848,9 @@ Handle<Object> CompilationCacheTable::Lookup(Handle<String> src,
                       RelocInfo::kNoPosition);
   int entry = FindEntry(&key);
   if (entry == kNotFound) return isolate->factory()->undefined_value();
-  return Handle<Object>(get(EntryToIndex(entry) + 1), isolate);
+  int index = EntryToIndex(entry);
+  if (!get(index)->IsFixedArray()) return isolate->factory()->undefined_value();
+  return Handle<Object>(get(index + 1), isolate);
 }
 
 
@@ -14854,6 +14863,8 @@ Handle<Object> CompilationCacheTable::LookupEval(
   StringSharedKey key(src, outer_info, strict_mode, scope_position);
   int entry = FindEntry(&key);
   if (entry == kNotFound) return isolate->factory()->undefined_value();
+  int index = EntryToIndex(entry);
+  if (!get(index)->IsFixedArray()) return isolate->factory()->undefined_value();
   return Handle<Object>(get(EntryToIndex(entry) + 1), isolate);
 }
 
@@ -14876,11 +14887,20 @@ Handle<CompilationCacheTable> CompilationCacheTable::Put(
   Handle<SharedFunctionInfo> shared(context->closure()->shared());
   StringSharedKey key(src, shared, FLAG_use_strict ? STRICT : SLOPPY,
                       RelocInfo::kNoPosition);
+  int entry = cache->FindEntry(&key);
+  if (entry != kNotFound) {
+    Handle<Object> k = key.AsHandle(isolate);
+    cache->set(EntryToIndex(entry), *k);
+    cache->set(EntryToIndex(entry) + 1, *value);
+    return cache;
+  }
+
   cache = EnsureCapacity(cache, 1, &key);
-  Handle<Object> k = key.AsHandle(isolate);
-  int entry = cache->FindInsertionEntry(key.Hash());
+  entry = cache->FindInsertionEntry(key.Hash());
+  Handle<Object> k =
+      isolate->factory()->NewNumber(static_cast<double>(key.Hash()));
   cache->set(EntryToIndex(entry), *k);
-  cache->set(EntryToIndex(entry) + 1, *value);
+  cache->set(EntryToIndex(entry) + 1, Smi::FromInt(kHashGenerations));
   cache->ElementAdded();
   return cache;
 }
@@ -14892,11 +14912,20 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutEval(
     int scope_position) {
   Isolate* isolate = cache->GetIsolate();
   StringSharedKey key(src, outer_info, value->strict_mode(), scope_position);
+  int entry = cache->FindEntry(&key);
+  if (entry != kNotFound) {
+    Handle<Object> k = key.AsHandle(isolate);
+    cache->set(EntryToIndex(entry), *k);
+    cache->set(EntryToIndex(entry) + 1, *value);
+    return cache;
+  }
+
   cache = EnsureCapacity(cache, 1, &key);
-  Handle<Object> k = key.AsHandle(isolate);
-  int entry = cache->FindInsertionEntry(key.Hash());
+  entry = cache->FindInsertionEntry(key.Hash());
+  Handle<Object> k =
+      isolate->factory()->NewNumber(static_cast<double>(key.Hash()));
   cache->set(EntryToIndex(entry), *k);
-  cache->set(EntryToIndex(entry) + 1, *value);
+  cache->set(EntryToIndex(entry) + 1, Smi::FromInt(kHashGenerations));
   cache->ElementAdded();
   return cache;
 }
@@ -14917,6 +14946,35 @@ Handle<CompilationCacheTable> CompilationCacheTable::PutRegExp(
 }
 
 
+void CompilationCacheTable::Age() {
+  DisallowHeapAllocation no_allocation;
+  Object* the_hole_value = GetHeap()->the_hole_value();
+  for (int entry = 0, size = Capacity(); entry < size; entry++) {
+    int entry_index = EntryToIndex(entry);
+    int value_index = entry_index + 1;
+
+    if (get(entry_index)->IsNumber()) {
+      Smi* count = Smi::cast(get(value_index));
+      count = Smi::FromInt(count->value() - 1);
+      if (count->value() == 0) {
+        NoWriteBarrierSet(this, entry_index, the_hole_value);
+        NoWriteBarrierSet(this, value_index, the_hole_value);
+        ElementRemoved();
+      } else {
+        NoWriteBarrierSet(this, value_index, count);
+      }
+    } else if (get(entry_index)->IsFixedArray()) {
+      SharedFunctionInfo* info = SharedFunctionInfo::cast(get(value_index));
+      if (info->code()->kind() != Code::FUNCTION || info->code()->IsOld()) {
+        NoWriteBarrierSet(this, entry_index, the_hole_value);
+        NoWriteBarrierSet(this, value_index, the_hole_value);
+        ElementRemoved();
+      }
+    }
+  }
+}
+
+
 void CompilationCacheTable::Remove(Object* value) {
   DisallowHeapAllocation no_allocation;
   Object* the_hole_value = GetHeap()->the_hole_value();
index fb94744..985d227 100644 (file)
@@ -7941,6 +7941,17 @@ class CompilationCacheShape : public BaseShape<HashTableKey*> {
 };
 
 
+// This cache is used in two different variants. For regexp caching, it simply
+// maps identifying info of the regexp to the cached regexp object. Scripts and
+// eval code only gets cached after a second probe for the code object. To do
+// so, on first "put" only a hash identifying the source is entered into the
+// cache, mapping it to a lifetime count of the hash. On each call to Age all
+// such lifetimes get reduced, and removed once they reach zero. If a second put
+// is called while such a hash is live in the cache, the hash gets replaced by
+// an actual cache entry. Age also removes stale live entries from the cache.
+// Such entries are identified by SharedFunctionInfos pointing to either the
+// recompilation stub, or to "old" code. This avoids memory leaks due to
+// premature caching of scripts and eval strings that are never needed later.
 class CompilationCacheTable: public HashTable<CompilationCacheTable,
                                               CompilationCacheShape,
                                               HashTableKey*> {
@@ -7962,6 +7973,8 @@ class CompilationCacheTable: public HashTable<CompilationCacheTable,
       Handle<CompilationCacheTable> cache, Handle<String> src,
       JSRegExp::Flags flags, Handle<FixedArray> value);
   void Remove(Object* value);
+  void Age();
+  static const int kHashGenerations = 10;
 
   DECLARE_CAST(CompilationCacheTable)
 
index e652282..6ba59cb 100644 (file)
@@ -1375,6 +1375,98 @@ TEST(TestCodeFlushingIncrementalAbort) {
 }
 
 
+TEST(CompilationCacheCachingBehavior) {
+  // If we do not flush code, or have the compilation cache turned off, this
+  // test is invalid.
+  if (!FLAG_flush_code || !FLAG_flush_code_incrementally ||
+      !FLAG_compilation_cache) {
+    return;
+  }
+  CcTest::InitializeVM();
+  Isolate* isolate = CcTest::i_isolate();
+  Factory* factory = isolate->factory();
+  Heap* heap = isolate->heap();
+  CompilationCache* compilation_cache = isolate->compilation_cache();
+
+  v8::HandleScope scope(CcTest::isolate());
+  const char* raw_source =
+      "function foo() {"
+      "  var x = 42;"
+      "  var y = 42;"
+      "  var z = x + y;"
+      "};"
+      "foo()";
+  Handle<String> source = factory->InternalizeUtf8String(raw_source);
+  Handle<Context> native_context = isolate->native_context();
+
+  {
+    v8::HandleScope scope(CcTest::isolate());
+    CompileRun(raw_source);
+  }
+
+  // On first compilation, only a hash is inserted in the code cache. We can't
+  // find that value.
+  MaybeHandle<SharedFunctionInfo> info = compilation_cache->LookupScript(
+      source, Handle<Object>(), 0, 0, true, native_context);
+  CHECK(info.is_null());
+
+  {
+    v8::HandleScope scope(CcTest::isolate());
+    CompileRun(raw_source);
+  }
+
+  // On second compilation, the hash is replaced by a real cache entry mapping
+  // the source to the shared function info containing the code.
+  info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+                                         native_context);
+  CHECK(!info.is_null());
+
+  heap->CollectAllGarbage(Heap::kNoGCFlags);
+
+  // On second compilation, the hash is replaced by a real cache entry mapping
+  // the source to the shared function info containing the code.
+  info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+                                         native_context);
+  CHECK(!info.is_null());
+
+  while (!info.ToHandleChecked()->code()->IsOld()) {
+    info.ToHandleChecked()->code()->MakeOlder(NO_MARKING_PARITY);
+  }
+
+  heap->CollectAllGarbage(Heap::kNoGCFlags);
+  // Ensure code aging cleared the entry from the cache.
+  info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+                                         native_context);
+  CHECK(info.is_null());
+
+  {
+    v8::HandleScope scope(CcTest::isolate());
+    CompileRun(raw_source);
+  }
+
+  // On first compilation, only a hash is inserted in the code cache. We can't
+  // find that value.
+  info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+                                         native_context);
+  CHECK(info.is_null());
+
+  for (int i = 0; i < CompilationCacheTable::kHashGenerations; i++) {
+    compilation_cache->MarkCompactPrologue();
+  }
+
+  {
+    v8::HandleScope scope(CcTest::isolate());
+    CompileRun(raw_source);
+  }
+
+  // If we aged the cache before caching the script, ensure that we didn't cache
+  // on next compilation.
+  info = compilation_cache->LookupScript(source, Handle<Object>(), 0, 0, true,
+                                         native_context);
+  CHECK(info.is_null());
+}
+
+
 // Count the number of native contexts in the weak list of native contexts.
 int CountNativeContexts() {
   int count = 0;