From: verwaest@chromium.org Date: Fri, 31 Oct 2014 14:51:48 +0000 (+0000) Subject: Changing the aging mechanism for script and eval caches. X-Git-Tag: upstream/4.7.83~5969 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=604672e87fc2b60bfab877865fa9a9c1da02900e;p=platform%2Fupstream%2Fv8.git Changing the aging mechanism for script and eval caches. 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 --- diff --git a/src/compilation-cache.cc b/src/compilation-cache.cc index aab2fe5..6c9f95a 100644 --- a/src/compilation-cache.cc +++ b/src/compilation-cache.cc @@ -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 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 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 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. diff --git a/src/compilation-cache.h b/src/compilation-cache.h index 6799b1c..a7c84b7 100644 --- a/src/compilation-cache.h +++ b/src/compilation-cache.h @@ -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); }; diff --git a/src/objects.cc b/src/objects.cc index ea07582..7cc9266 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -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(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(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 CompilationCacheTable::Lookup(Handle src, RelocInfo::kNoPosition); int entry = FindEntry(&key); if (entry == kNotFound) return isolate->factory()->undefined_value(); - return Handle(get(EntryToIndex(entry) + 1), isolate); + int index = EntryToIndex(entry); + if (!get(index)->IsFixedArray()) return isolate->factory()->undefined_value(); + return Handle(get(index + 1), isolate); } @@ -14854,6 +14863,8 @@ Handle 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(get(EntryToIndex(entry) + 1), isolate); } @@ -14876,11 +14887,20 @@ Handle CompilationCacheTable::Put( Handle shared(context->closure()->shared()); StringSharedKey key(src, shared, FLAG_use_strict ? STRICT : SLOPPY, RelocInfo::kNoPosition); + int entry = cache->FindEntry(&key); + if (entry != kNotFound) { + Handle k = key.AsHandle(isolate); + cache->set(EntryToIndex(entry), *k); + cache->set(EntryToIndex(entry) + 1, *value); + return cache; + } + cache = EnsureCapacity(cache, 1, &key); - Handle k = key.AsHandle(isolate); - int entry = cache->FindInsertionEntry(key.Hash()); + entry = cache->FindInsertionEntry(key.Hash()); + Handle k = + isolate->factory()->NewNumber(static_cast(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::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 k = key.AsHandle(isolate); + cache->set(EntryToIndex(entry), *k); + cache->set(EntryToIndex(entry) + 1, *value); + return cache; + } + cache = EnsureCapacity(cache, 1, &key); - Handle k = key.AsHandle(isolate); - int entry = cache->FindInsertionEntry(key.Hash()); + entry = cache->FindInsertionEntry(key.Hash()); + Handle k = + isolate->factory()->NewNumber(static_cast(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::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(); diff --git a/src/objects.h b/src/objects.h index fb94744..985d227 100644 --- a/src/objects.h +++ b/src/objects.h @@ -7941,6 +7941,17 @@ class CompilationCacheShape : public BaseShape { }; +// 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 { @@ -7962,6 +7973,8 @@ class CompilationCacheTable: public HashTable cache, Handle src, JSRegExp::Flags flags, Handle value); void Remove(Object* value); + void Age(); + static const int kHashGenerations = 10; DECLARE_CAST(CompilationCacheTable) diff --git a/test/cctest/test-heap.cc b/test/cctest/test-heap.cc index e652282..6ba59cb 100644 --- a/test/cctest/test-heap.cc +++ b/test/cctest/test-heap.cc @@ -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 source = factory->InternalizeUtf8String(raw_source); + Handle 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 info = compilation_cache->LookupScript( + source, Handle(), 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(), 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(), 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(), 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(), 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(), 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;