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
This commit is contained in:
verwaest@chromium.org 2014-10-31 14:51:48 +00:00
parent 3a3d5e741a
commit 604672e87f
5 changed files with 183 additions and 36 deletions

View 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.

View 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);
};

View 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);
cache = EnsureCapacity(cache, 1, &key);
int entry = cache->FindEntry(&key);
if (entry != kNotFound) {
Handle<Object> k = key.AsHandle(isolate);
int entry = cache->FindInsertionEntry(key.Hash());
cache->set(EntryToIndex(entry), *k);
cache->set(EntryToIndex(entry) + 1, *value);
return cache;
}
cache = EnsureCapacity(cache, 1, &key);
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, 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);
cache = EnsureCapacity(cache, 1, &key);
int entry = cache->FindEntry(&key);
if (entry != kNotFound) {
Handle<Object> k = key.AsHandle(isolate);
int entry = cache->FindInsertionEntry(key.Hash());
cache->set(EntryToIndex(entry), *k);
cache->set(EntryToIndex(entry) + 1, *value);
return cache;
}
cache = EnsureCapacity(cache, 1, &key);
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, 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();

View 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)

View 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;