Add multiple generations (5) to the script compilation cache
to allow scripts that are used alot to survive a number of GCs in the compilation cache. Review URL: http://codereview.chromium.org/113445 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1955 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
4a12504f89
commit
2d50e31438
@ -32,12 +32,20 @@
|
|||||||
namespace v8 { namespace internal {
|
namespace v8 { namespace internal {
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
NUMBER_OF_ENTRY_KINDS = CompilationCache::LAST_ENTRY + 1
|
// The number of script generations tell how many GCs a script can
|
||||||
|
// survive in the compilation cache, before it will be flushed if it
|
||||||
|
// hasn't been used.
|
||||||
|
NUMBER_OF_SCRIPT_GENERATIONS = 5,
|
||||||
|
|
||||||
|
// The compilation cache consists of tables - one for each entry
|
||||||
|
// kind plus extras for the script generations.
|
||||||
|
NUMBER_OF_TABLE_ENTRIES =
|
||||||
|
CompilationCache::LAST_ENTRY + NUMBER_OF_SCRIPT_GENERATIONS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Keep separate tables for the different entry kinds.
|
// Keep separate tables for the different entry kinds.
|
||||||
static Object* tables[NUMBER_OF_ENTRY_KINDS] = { 0, };
|
static Object* tables[NUMBER_OF_TABLE_ENTRIES] = { 0, };
|
||||||
|
|
||||||
|
|
||||||
static Handle<CompilationCacheTable> AllocateTable(int size) {
|
static Handle<CompilationCacheTable> AllocateTable(int size) {
|
||||||
@ -121,41 +129,52 @@ static bool HasOrigin(Handle<JSFunction> boilerplate,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Handle<JSFunction> Lookup(Handle<String> source,
|
// TODO(245): Need to allow identical code from different contexts to
|
||||||
CompilationCache::Entry entry) {
|
// be cached in the same script generation. Currently the first use
|
||||||
// Make sure not to leak the table into the surrounding handle
|
// will be cached, but subsequent code from different source / line
|
||||||
// scope. Otherwise, we risk keeping old tables around even after
|
// won't.
|
||||||
// having cleared the cache.
|
|
||||||
Object* result;
|
|
||||||
{ HandleScope scope;
|
|
||||||
Handle<CompilationCacheTable> table = GetTable(entry);
|
|
||||||
result = table->Lookup(*source);
|
|
||||||
}
|
|
||||||
if (result->IsJSFunction()) {
|
|
||||||
return Handle<JSFunction>(JSFunction::cast(result));
|
|
||||||
} else {
|
|
||||||
return Handle<JSFunction>::null();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO(245): Need to allow identical code from different contexts to be
|
|
||||||
// cached. Currently the first use will be cached, but subsequent code
|
|
||||||
// from different source / line won't.
|
|
||||||
Handle<JSFunction> CompilationCache::LookupScript(Handle<String> source,
|
Handle<JSFunction> CompilationCache::LookupScript(Handle<String> source,
|
||||||
Handle<Object> name,
|
Handle<Object> name,
|
||||||
int line_offset,
|
int line_offset,
|
||||||
int column_offset) {
|
int column_offset) {
|
||||||
Handle<JSFunction> result = Lookup(source, SCRIPT);
|
Object* result = NULL;
|
||||||
if (result.is_null()) {
|
Entry generation = SCRIPT; // First generation.
|
||||||
Counters::compilation_cache_misses.Increment();
|
|
||||||
} else if (HasOrigin(result, name, line_offset, column_offset)) {
|
// Probe the script generation tables. Make sure not to leak handles
|
||||||
Counters::compilation_cache_hits.Increment();
|
// into the caller's handle scope.
|
||||||
} else {
|
{ HandleScope scope;
|
||||||
result = Handle<JSFunction>::null();
|
while (generation < SCRIPT + NUMBER_OF_SCRIPT_GENERATIONS) {
|
||||||
Counters::compilation_cache_misses.Increment();
|
Handle<CompilationCacheTable> table = GetTable(generation);
|
||||||
|
Handle<Object> probe(table->Lookup(*source));
|
||||||
|
if (probe->IsJSFunction()) {
|
||||||
|
Handle<JSFunction> boilerplate = Handle<JSFunction>::cast(probe);
|
||||||
|
// Break when we've found a suitable boilerplate function that
|
||||||
|
// matches the origin.
|
||||||
|
if (HasOrigin(boilerplate, name, line_offset, column_offset)) {
|
||||||
|
result = *boilerplate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Go to the next generation.
|
||||||
|
generation = static_cast<Entry>(generation + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once outside the menacles 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.
|
||||||
|
if (result != NULL) {
|
||||||
|
Handle<JSFunction> boilerplate(JSFunction::cast(result));
|
||||||
|
ASSERT(HasOrigin(boilerplate, name, line_offset, column_offset));
|
||||||
|
// If the script was found in a later generation, we promote it to
|
||||||
|
// the first generation to let it survive longer in the cache.
|
||||||
|
if (generation != SCRIPT) PutScript(source, boilerplate);
|
||||||
|
Counters::compilation_cache_hits.Increment();
|
||||||
|
return boilerplate;
|
||||||
|
} else {
|
||||||
|
Counters::compilation_cache_misses.Increment();
|
||||||
|
return Handle<JSFunction>::null();
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -216,14 +235,25 @@ void CompilationCache::PutRegExp(Handle<String> source,
|
|||||||
|
|
||||||
|
|
||||||
void CompilationCache::Clear() {
|
void CompilationCache::Clear() {
|
||||||
for (int i = 0; i < NUMBER_OF_ENTRY_KINDS; i++) {
|
for (int i = 0; i < NUMBER_OF_TABLE_ENTRIES; i++) {
|
||||||
tables[i] = Heap::undefined_value();
|
tables[i] = Heap::undefined_value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CompilationCache::Iterate(ObjectVisitor* v) {
|
void CompilationCache::Iterate(ObjectVisitor* v) {
|
||||||
v->VisitPointers(&tables[0], &tables[NUMBER_OF_ENTRY_KINDS]);
|
v->VisitPointers(&tables[0], &tables[NUMBER_OF_TABLE_ENTRIES]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CompilationCache::MarkCompactPrologue() {
|
||||||
|
ASSERT(LAST_ENTRY == SCRIPT);
|
||||||
|
for (int i = NUMBER_OF_TABLE_ENTRIES - 1; i > SCRIPT; i--) {
|
||||||
|
tables[i] = tables[i - 1];
|
||||||
|
}
|
||||||
|
for (int j = 0; j <= LAST_ENTRY; j++) {
|
||||||
|
tables[j] = Heap::undefined_value();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,11 +40,11 @@ class CompilationCache {
|
|||||||
// scripts and evals. Internally, we use separate caches to avoid
|
// scripts and evals. Internally, we use separate caches to avoid
|
||||||
// getting the wrong kind of entry when looking up.
|
// getting the wrong kind of entry when looking up.
|
||||||
enum Entry {
|
enum Entry {
|
||||||
SCRIPT,
|
|
||||||
EVAL_GLOBAL,
|
EVAL_GLOBAL,
|
||||||
EVAL_CONTEXTUAL,
|
EVAL_CONTEXTUAL,
|
||||||
REGEXP,
|
REGEXP,
|
||||||
LAST_ENTRY = REGEXP
|
SCRIPT,
|
||||||
|
LAST_ENTRY = SCRIPT
|
||||||
};
|
};
|
||||||
|
|
||||||
// Finds the script function boilerplate for a source
|
// Finds the script function boilerplate for a source
|
||||||
@ -93,10 +93,8 @@ class CompilationCache {
|
|||||||
|
|
||||||
// Notify the cache that a mark-sweep garbage collection is about to
|
// Notify the cache that a mark-sweep garbage collection is about to
|
||||||
// take place. This is used to retire entries from the cache to
|
// take place. This is used to retire entries from the cache to
|
||||||
// avoid keeping them alive too long without using them. For now, we
|
// avoid keeping them alive too long without using them.
|
||||||
// just clear the cache but we should consider are more
|
static void MarkCompactPrologue();
|
||||||
// sophisticated LRU scheme.
|
|
||||||
static void MarkCompactPrologue() { Clear(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "v8.h"
|
#include "v8.h"
|
||||||
|
|
||||||
#include "api.h"
|
#include "api.h"
|
||||||
|
#include "compilation-cache.h"
|
||||||
#include "snapshot.h"
|
#include "snapshot.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "top.h"
|
#include "top.h"
|
||||||
@ -464,6 +465,7 @@ THREADED_TEST(ScriptUsingStringResource) {
|
|||||||
v8::internal::Heap::CollectAllGarbage();
|
v8::internal::Heap::CollectAllGarbage();
|
||||||
CHECK_EQ(0, TestResource::dispose_count);
|
CHECK_EQ(0, TestResource::dispose_count);
|
||||||
}
|
}
|
||||||
|
v8::internal::CompilationCache::Clear();
|
||||||
v8::internal::Heap::CollectAllGarbage();
|
v8::internal::Heap::CollectAllGarbage();
|
||||||
CHECK_EQ(1, TestResource::dispose_count);
|
CHECK_EQ(1, TestResource::dispose_count);
|
||||||
}
|
}
|
||||||
@ -484,6 +486,7 @@ THREADED_TEST(ScriptUsingAsciiStringResource) {
|
|||||||
v8::internal::Heap::CollectAllGarbage();
|
v8::internal::Heap::CollectAllGarbage();
|
||||||
CHECK_EQ(0, TestAsciiResource::dispose_count);
|
CHECK_EQ(0, TestAsciiResource::dispose_count);
|
||||||
}
|
}
|
||||||
|
v8::internal::CompilationCache::Clear();
|
||||||
v8::internal::Heap::CollectAllGarbage();
|
v8::internal::Heap::CollectAllGarbage();
|
||||||
CHECK_EQ(1, TestAsciiResource::dispose_count);
|
CHECK_EQ(1, TestAsciiResource::dispose_count);
|
||||||
}
|
}
|
||||||
@ -505,6 +508,7 @@ THREADED_TEST(ScriptMakingExternalString) {
|
|||||||
v8::internal::Heap::CollectAllGarbage();
|
v8::internal::Heap::CollectAllGarbage();
|
||||||
CHECK_EQ(0, TestResource::dispose_count);
|
CHECK_EQ(0, TestResource::dispose_count);
|
||||||
}
|
}
|
||||||
|
v8::internal::CompilationCache::Clear();
|
||||||
v8::internal::Heap::CollectAllGarbage();
|
v8::internal::Heap::CollectAllGarbage();
|
||||||
CHECK_EQ(1, TestResource::dispose_count);
|
CHECK_EQ(1, TestResource::dispose_count);
|
||||||
}
|
}
|
||||||
@ -527,6 +531,7 @@ THREADED_TEST(ScriptMakingExternalAsciiString) {
|
|||||||
v8::internal::Heap::CollectAllGarbage();
|
v8::internal::Heap::CollectAllGarbage();
|
||||||
CHECK_EQ(0, TestAsciiResource::dispose_count);
|
CHECK_EQ(0, TestAsciiResource::dispose_count);
|
||||||
}
|
}
|
||||||
|
v8::internal::CompilationCache::Clear();
|
||||||
v8::internal::Heap::CollectAllGarbage();
|
v8::internal::Heap::CollectAllGarbage();
|
||||||
CHECK_EQ(1, TestAsciiResource::dispose_count);
|
CHECK_EQ(1, TestAsciiResource::dispose_count);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "v8.h"
|
#include "v8.h"
|
||||||
|
|
||||||
#include "api.h"
|
#include "api.h"
|
||||||
|
#include "compilation-cache.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "stub-cache.h"
|
#include "stub-cache.h"
|
||||||
@ -1678,6 +1679,11 @@ TEST(ScriptBreakPointIgnoreCount) {
|
|||||||
}
|
}
|
||||||
CHECK_EQ(5, break_point_hit_count);
|
CHECK_EQ(5, break_point_hit_count);
|
||||||
|
|
||||||
|
// BUG(343): It should not really be necessary to clear the
|
||||||
|
// compilation cache here, but right now the debugger relies on the
|
||||||
|
// script being recompiled, not just fetched from the cache.
|
||||||
|
i::CompilationCache::Clear();
|
||||||
|
|
||||||
// Reload the script and get f again checking that the ignore survives.
|
// Reload the script and get f again checking that the ignore survives.
|
||||||
v8::Script::Compile(script, &origin)->Run();
|
v8::Script::Compile(script, &origin)->Run();
|
||||||
f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f")));
|
f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f")));
|
||||||
|
Loading…
Reference in New Issue
Block a user