Clean JS function results cache on each major GC.
We don't want to retain cached objects for too long. Review URL: http://codereview.chromium.org/1780001 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4582 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
16d3811d50
commit
6617fac3d4
@ -1335,14 +1335,12 @@ bool Genesis::InstallNatives() {
|
||||
static FixedArray* CreateCache(int size, JSFunction* factory) {
|
||||
// Caches are supposed to live for a long time, allocate in old space.
|
||||
int array_size = JSFunctionResultCache::kEntriesIndex + 2 * size;
|
||||
Handle<FixedArray> cache =
|
||||
Factory::NewFixedArrayWithHoles(array_size, TENURED);
|
||||
// Cannot use cast as object is not fully initialized yet.
|
||||
JSFunctionResultCache* cache = reinterpret_cast<JSFunctionResultCache*>(
|
||||
*Factory::NewFixedArrayWithHoles(array_size, TENURED));
|
||||
cache->set(JSFunctionResultCache::kFactoryIndex, factory);
|
||||
cache->set(JSFunctionResultCache::kFingerIndex,
|
||||
Smi::FromInt(JSFunctionResultCache::kEntriesIndex));
|
||||
cache->set(JSFunctionResultCache::kCacheSizeIndex,
|
||||
Smi::FromInt(JSFunctionResultCache::kEntriesIndex));
|
||||
return *cache;
|
||||
cache->MakeZeroSize();
|
||||
return cache;
|
||||
}
|
||||
|
||||
|
||||
|
23
src/heap.cc
23
src/heap.cc
@ -306,6 +306,7 @@ void Heap::ReportStatisticsAfterGC() {
|
||||
|
||||
void Heap::GarbageCollectionPrologue() {
|
||||
TranscendentalCache::Clear();
|
||||
ClearJSFunctionResultCaches();
|
||||
gc_count_++;
|
||||
unflattened_strings_length_ = 0;
|
||||
#ifdef DEBUG
|
||||
@ -541,6 +542,28 @@ void Heap::EnsureFromSpaceIsCommitted() {
|
||||
}
|
||||
|
||||
|
||||
class ClearThreadJSFunctionResultCachesVisitor: public ThreadVisitor {
|
||||
virtual void VisitThread(ThreadLocalTop* top) {
|
||||
Context* context = top->context_;
|
||||
if (context == NULL) return;
|
||||
|
||||
FixedArray* caches =
|
||||
context->global()->global_context()->jsfunction_result_caches();
|
||||
int length = caches->length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
JSFunctionResultCache::cast(caches->get(i))->Clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void Heap::ClearJSFunctionResultCaches() {
|
||||
if (Bootstrapper::IsActive()) return;
|
||||
ClearThreadJSFunctionResultCachesVisitor visitor;
|
||||
ThreadManager::IterateThreads(&visitor);
|
||||
}
|
||||
|
||||
|
||||
void Heap::PerformGarbageCollection(AllocationSpace space,
|
||||
GarbageCollector collector,
|
||||
GCTracer* tracer) {
|
||||
|
@ -1178,6 +1178,8 @@ class Heap : public AllStatic {
|
||||
HeapObject* target,
|
||||
int size);
|
||||
|
||||
static void ClearJSFunctionResultCaches();
|
||||
|
||||
#if defined(DEBUG) || defined(ENABLE_LOGGING_AND_PROFILING)
|
||||
// Record the copy of an object in the NewSpace's statistics.
|
||||
static void RecordCopiedObject(HeapObject* obj);
|
||||
|
@ -1328,6 +1328,32 @@ bool DescriptorArray::IsSortedNoDuplicates() {
|
||||
}
|
||||
|
||||
|
||||
void JSFunctionResultCache::JSFunctionResultCacheVerify() {
|
||||
JSFunction::cast(get(kFactoryIndex))->Verify();
|
||||
|
||||
int size = Smi::cast(get(kCacheSizeIndex))->value();
|
||||
ASSERT(kEntriesIndex <= size);
|
||||
ASSERT(size <= length());
|
||||
ASSERT_EQ(0, size % kEntrySize);
|
||||
|
||||
int finger = Smi::cast(get(kFingerIndex))->value();
|
||||
ASSERT(kEntriesIndex <= finger);
|
||||
ASSERT(finger < size || finger == kEntriesIndex);
|
||||
ASSERT_EQ(0, finger % kEntrySize);
|
||||
|
||||
if (FLAG_enable_slow_asserts) {
|
||||
for (int i = kEntriesIndex; i < size; i++) {
|
||||
ASSERT(!get(i)->IsTheHole());
|
||||
get(i)->Verify();
|
||||
}
|
||||
for (int i = size; i < length(); i++) {
|
||||
ASSERT(get(i)->IsTheHole());
|
||||
get(i)->Verify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // DEBUG
|
||||
|
||||
} } // namespace v8::internal
|
||||
|
@ -569,6 +569,22 @@ bool Object::IsSymbolTable() {
|
||||
}
|
||||
|
||||
|
||||
bool Object::IsJSFunctionResultCache() {
|
||||
if (!IsFixedArray()) return false;
|
||||
FixedArray* self = FixedArray::cast(this);
|
||||
int length = self->length();
|
||||
if (length < JSFunctionResultCache::kEntriesIndex) return false;
|
||||
if ((length - JSFunctionResultCache::kEntriesIndex)
|
||||
% JSFunctionResultCache::kEntrySize != 0) {
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
reinterpret_cast<JSFunctionResultCache*>(this)->JSFunctionResultCacheVerify();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Object::IsCompilationCacheTable() {
|
||||
return IsHashTable();
|
||||
}
|
||||
@ -1594,6 +1610,7 @@ void NumberDictionary::set_requires_slow_elements() {
|
||||
CAST_ACCESSOR(FixedArray)
|
||||
CAST_ACCESSOR(DescriptorArray)
|
||||
CAST_ACCESSOR(SymbolTable)
|
||||
CAST_ACCESSOR(JSFunctionResultCache)
|
||||
CAST_ACCESSOR(CompilationCacheTable)
|
||||
CAST_ACCESSOR(CodeCacheHashTable)
|
||||
CAST_ACCESSOR(MapCache)
|
||||
@ -1836,6 +1853,20 @@ void ExternalTwoByteString::set_resource(
|
||||
}
|
||||
|
||||
|
||||
void JSFunctionResultCache::MakeZeroSize() {
|
||||
set(kFingerIndex, Smi::FromInt(kEntriesIndex));
|
||||
set(kCacheSizeIndex, Smi::FromInt(kEntriesIndex));
|
||||
}
|
||||
|
||||
|
||||
void JSFunctionResultCache::Clear() {
|
||||
int cache_size = Smi::cast(get(kCacheSizeIndex))->value();
|
||||
Object** entries_start = RawField(this, OffsetOfElementAt(kEntriesIndex));
|
||||
MemsetPointer(entries_start, Heap::the_hole_value(), cache_size);
|
||||
MakeZeroSize();
|
||||
}
|
||||
|
||||
|
||||
byte ByteArray::get(int index) {
|
||||
ASSERT(index >= 0 && index < this->length());
|
||||
return READ_BYTE_FIELD(this, kHeaderSize + index * kCharSize);
|
||||
|
@ -606,6 +606,7 @@ class Object BASE_EMBEDDED {
|
||||
inline bool IsHashTable();
|
||||
inline bool IsDictionary();
|
||||
inline bool IsSymbolTable();
|
||||
inline bool IsJSFunctionResultCache();
|
||||
inline bool IsCompilationCacheTable();
|
||||
inline bool IsCodeCacheHashTable();
|
||||
inline bool IsMapCache();
|
||||
@ -2326,6 +2327,16 @@ class JSFunctionResultCache: public FixedArray {
|
||||
static const int kEntriesIndex = kDummyIndex + 1;
|
||||
|
||||
static const int kEntrySize = 2; // key + value
|
||||
|
||||
inline void MakeZeroSize();
|
||||
inline void Clear();
|
||||
|
||||
// Casting
|
||||
static inline JSFunctionResultCache* cast(Object* obj);
|
||||
|
||||
#ifdef DEBUG
|
||||
void JSFunctionResultCacheVerify();
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
@ -50,3 +50,87 @@ TEST(Preemption) {
|
||||
|
||||
script->Run();
|
||||
}
|
||||
|
||||
|
||||
enum Turn {
|
||||
FILL_CACHE,
|
||||
CLEAN_CACHE,
|
||||
SECOND_TIME_FILL_CACHE,
|
||||
DONE
|
||||
};
|
||||
|
||||
static Turn turn = FILL_CACHE;
|
||||
|
||||
|
||||
class ThreadA: public v8::internal::Thread {
|
||||
public:
|
||||
void Run() {
|
||||
v8::Locker locker;
|
||||
v8::HandleScope scope;
|
||||
v8::Context::Scope context_scope(v8::Context::New());
|
||||
|
||||
CHECK_EQ(FILL_CACHE, turn);
|
||||
|
||||
// Fill String.search cache.
|
||||
v8::Handle<v8::Script> script = v8::Script::Compile(
|
||||
v8::String::New(
|
||||
"for (var i = 0; i < 3; i++) {"
|
||||
" var result = \"a\".search(\"a\");"
|
||||
" if (result != 0) throw \"result: \" + result + \" @\" + i;"
|
||||
"};"
|
||||
"true"));
|
||||
CHECK(script->Run()->IsTrue());
|
||||
|
||||
turn = CLEAN_CACHE;
|
||||
do {
|
||||
{
|
||||
v8::Unlocker unlocker;
|
||||
Thread::YieldCPU();
|
||||
}
|
||||
} while (turn != SECOND_TIME_FILL_CACHE);
|
||||
|
||||
// Rerun the script.
|
||||
CHECK(script->Run()->IsTrue());
|
||||
|
||||
turn = DONE;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class ThreadB: public v8::internal::Thread {
|
||||
public:
|
||||
void Run() {
|
||||
do {
|
||||
{
|
||||
v8::Locker locker;
|
||||
if (turn == CLEAN_CACHE) {
|
||||
v8::HandleScope scope;
|
||||
v8::Context::Scope context_scope(v8::Context::New());
|
||||
|
||||
// Clear the caches by forcing major GC.
|
||||
v8::internal::Heap::CollectAllGarbage(false);
|
||||
turn = SECOND_TIME_FILL_CACHE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Thread::YieldCPU();
|
||||
} while (true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST(JSFunctionResultCachesInTwoThreads) {
|
||||
v8::V8::Initialize();
|
||||
|
||||
ThreadA threadA;
|
||||
ThreadB threadB;
|
||||
|
||||
threadA.Start();
|
||||
threadB.Start();
|
||||
|
||||
threadA.Join();
|
||||
threadB.Join();
|
||||
|
||||
CHECK_EQ(DONE, turn);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user