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:
parent
3a3d5e741a
commit
604672e87f
@ -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.
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user