Commit 31882103 authored by jkummerow's avatar jkummerow Committed by Commit bot

Refactor Maps' code_cache

Most maps have a small code cache (often only one entry), so this patch
optimizes memory consumption of such cases by using plain FixedArrays,
only switching to CodeCacheHashTables when the number of cached entries
gets so large that linear-scan lookups get too slow.

On loading inbox.google.com, this gets the aggregate size of all maps'
code caches (there are about 13,600 of them) from 4300 KB to 970 KB.

Review-Url: https://codereview.chromium.org/2021373002
Cr-Commit-Position: refs/heads/master@{#36681}
parent 4f205165
......@@ -197,7 +197,7 @@ void ObjectStatsCollector::RecordMapStats(Map* map, HeapObject* obj) {
fixed_array_size);
}
if (map_obj->has_code_cache()) {
FixedArray* cache = FixedArray::cast(map_obj->code_cache());
FixedArray* cache = map_obj->code_cache();
heap->object_stats_->RecordFixedArraySubTypeStats(MAP_CODE_CACHE_SUB_TYPE,
cache->Size());
}
......
......@@ -4697,7 +4697,7 @@ bool Map::is_stable() {
bool Map::has_code_cache() {
// Code caches are always fixed arrays. The empty fixed array is used as a
// sentinel for an absent code cache.
return FixedArray::cast(code_cache())->length() != 0;
return code_cache()->length() != 0;
}
......@@ -5431,8 +5431,7 @@ void Map::SetBackPointer(Object* value, WriteBarrierMode mode) {
set_constructor_or_backpointer(value, mode);
}
ACCESSORS(Map, code_cache, Object, kCodeCacheOffset)
ACCESSORS(Map, code_cache, FixedArray, kCodeCacheOffset)
ACCESSORS(Map, dependent_code, DependentCode, kDependentCodeOffset)
ACCESSORS(Map, weak_cell_cache, Object, kWeakCellCacheOffset)
ACCESSORS(Map, constructor_or_backpointer, Object,
......
......@@ -9447,29 +9447,186 @@ Handle<Map> Map::CopyReplaceDescriptor(Handle<Map> map,
simple_flag);
}
// Helper class to manage a Map's code cache. The layout depends on the number
// of entries; this is worthwhile because most code caches are very small,
// but some are huge (thousands of entries).
// For zero entries, the EmptyFixedArray is used.
// For one entry, we use a 2-element FixedArray containing [name, code].
// For 2..100 entries, we use a FixedArray with linear lookups, the layout is:
// [0] - number of slots that are currently in use
// [1] - first name
// [2] - first code
// [3] - second name
// [4] - second code
// etc.
// For more than 128 entries, we use a CodeCacheHashTable.
class CodeCache : public AllStatic {
public:
// Returns the new cache, to be stored on the map.
static Handle<FixedArray> Put(Isolate* isolate, Handle<FixedArray> cache,
Handle<Name> name, Handle<Code> code) {
int length = cache->length();
if (length == 0) return PutFirstElement(isolate, name, code);
if (length == kEntrySize) {
return PutSecondElement(isolate, cache, name, code);
}
if (length <= kLinearMaxSize) {
Handle<FixedArray> result = PutLinearElement(isolate, cache, name, code);
if (!result.is_null()) return result;
// Fall through if linear storage is getting too large.
}
return PutHashTableElement(isolate, cache, name, code);
}
static Code* Lookup(FixedArray* cache, Name* name, Code::Flags flags) {
int length = cache->length();
if (length == 0) return nullptr;
if (length == kEntrySize) return OneElementLookup(cache, name, flags);
if (!cache->IsCodeCacheHashTable()) {
return LinearLookup(cache, name, flags);
} else {
return CodeCacheHashTable::cast(cache)->Lookup(name, flags);
}
}
private:
static const int kNameIndex = 0;
static const int kCodeIndex = 1;
static const int kEntrySize = 2;
static const int kLinearUsageIndex = 0;
static const int kLinearReservedSlots = 1;
static const int kLinearInitialCapacity = 2;
static const int kLinearMaxSize = 257; // == LinearSizeFor(128);
static const int kHashTableInitialCapacity = 200; // Number of entries.
static int LinearSizeFor(int entries) {
return kLinearReservedSlots + kEntrySize * entries;
}
static int LinearNewSize(int old_size) {
int old_entries = (old_size - kLinearReservedSlots) / kEntrySize;
return LinearSizeFor(old_entries * 2);
}
static Code* OneElementLookup(FixedArray* cache, Name* name,
Code::Flags flags) {
DCHECK_EQ(cache->length(), kEntrySize);
if (cache->get(kNameIndex) != name) return nullptr;
Code* maybe_code = Code::cast(cache->get(kCodeIndex));
if (maybe_code->flags() != flags) return nullptr;
return maybe_code;
}
static Code* LinearLookup(FixedArray* cache, Name* name, Code::Flags flags) {
DCHECK_GE(cache->length(), kEntrySize);
DCHECK(!cache->IsCodeCacheHashTable());
int usage = GetLinearUsage(cache);
for (int i = kLinearReservedSlots; i < usage; i += kEntrySize) {
if (cache->get(i + kNameIndex) != name) continue;
Code* code = Code::cast(cache->get(i + kCodeIndex));
if (code->flags() == flags) return code;
}
return nullptr;
}
static Handle<FixedArray> PutFirstElement(Isolate* isolate, Handle<Name> name,
Handle<Code> code) {
Handle<FixedArray> cache = isolate->factory()->NewFixedArray(kEntrySize);
cache->set(kNameIndex, *name);
cache->set(kCodeIndex, *code);
return cache;
}
static Handle<FixedArray> PutSecondElement(Isolate* isolate,
Handle<FixedArray> cache,
Handle<Name> name,
Handle<Code> code) {
DCHECK_EQ(cache->length(), kEntrySize);
Handle<FixedArray> new_cache = isolate->factory()->NewFixedArray(
LinearSizeFor(kLinearInitialCapacity));
new_cache->set(kLinearReservedSlots + kNameIndex, cache->get(kNameIndex));
new_cache->set(kLinearReservedSlots + kCodeIndex, cache->get(kCodeIndex));
new_cache->set(LinearSizeFor(1) + kNameIndex, *name);
new_cache->set(LinearSizeFor(1) + kCodeIndex, *code);
new_cache->set(kLinearUsageIndex, Smi::FromInt(LinearSizeFor(2)));
return new_cache;
}
static Handle<FixedArray> PutLinearElement(Isolate* isolate,
Handle<FixedArray> cache,
Handle<Name> name,
Handle<Code> code) {
int length = cache->length();
int usage = GetLinearUsage(*cache);
DCHECK_LE(usage, length);
// Check if we need to grow.
if (usage == length) {
int new_length = LinearNewSize(length);
if (new_length > kLinearMaxSize) return Handle<FixedArray>::null();
Handle<FixedArray> new_cache =
isolate->factory()->NewFixedArray(new_length);
for (int i = kLinearReservedSlots; i < length; i++) {
new_cache->set(i, cache->get(i));
}
cache = new_cache;
}
// Store new entry.
DCHECK_GE(cache->length(), usage + kEntrySize);
cache->set(usage + kNameIndex, *name);
cache->set(usage + kCodeIndex, *code);
cache->set(kLinearUsageIndex, Smi::FromInt(usage + kEntrySize));
return cache;
}
static Handle<FixedArray> PutHashTableElement(Isolate* isolate,
Handle<FixedArray> cache,
Handle<Name> name,
Handle<Code> code) {
// Check if we need to transition from linear to hash table storage.
if (!cache->IsCodeCacheHashTable()) {
// Check that the initial hash table capacity is large enough.
DCHECK_EQ(kLinearMaxSize, LinearSizeFor(128));
STATIC_ASSERT(kHashTableInitialCapacity > 128);
int length = cache->length();
// Only migrate from linear storage when it's full.
DCHECK_EQ(length, GetLinearUsage(*cache));
DCHECK_EQ(length, kLinearMaxSize);
Handle<CodeCacheHashTable> table =
CodeCacheHashTable::New(isolate, kHashTableInitialCapacity);
HandleScope scope(isolate);
for (int i = kLinearReservedSlots; i < length; i += kEntrySize) {
Handle<Name> old_name(Name::cast(cache->get(i + kNameIndex)), isolate);
Handle<Code> old_code(Code::cast(cache->get(i + kCodeIndex)), isolate);
CodeCacheHashTable::Put(table, old_name, old_code);
}
cache = table;
}
// Store new entry.
DCHECK(cache->IsCodeCacheHashTable());
return CodeCacheHashTable::Put(Handle<CodeCacheHashTable>::cast(cache),
name, code);
}
static inline int GetLinearUsage(FixedArray* linear_cache) {
DCHECK_GT(linear_cache->length(), kEntrySize);
return Smi::cast(linear_cache->get(kLinearUsageIndex))->value();
}
};
void Map::UpdateCodeCache(Handle<Map> map,
Handle<Name> name,
Handle<Code> code) {
Isolate* isolate = map->GetIsolate();
HandleScope scope(isolate);
// Allocate the code cache if not present.
if (!map->has_code_cache()) {
Handle<Object> result =
CodeCacheHashTable::New(isolate, CodeCacheHashTable::kInitialSize);
map->set_code_cache(*result);
}
// Update the code cache.
Handle<CodeCacheHashTable> cache(CodeCacheHashTable::cast(map->code_cache()),
isolate);
Handle<Object> new_cache = CodeCacheHashTable::Put(cache, name, code);
Handle<FixedArray> cache(map->code_cache(), isolate);
Handle<FixedArray> new_cache = CodeCache::Put(isolate, cache, name, code);
map->set_code_cache(*new_cache);
}
Code* Map::LookupInCodeCache(Name* name, Code::Flags flags) {
if (!has_code_cache()) return nullptr;
return CodeCacheHashTable::cast(code_cache())->Lookup(name, flags);
return CodeCache::Lookup(code_cache(), name, flags);
}
......
......@@ -5841,7 +5841,7 @@ class Map: public HeapObject {
LayoutDescriptor* layout_descriptor);
// [stub cache]: contains stubs compiled for this map.
DECL_ACCESSORS(code_cache, Object)
DECL_ACCESSORS(code_cache, FixedArray)
// [dependent code]: list of optimized codes that weakly embed this map.
DECL_ACCESSORS(dependent_code, DependentCode)
......@@ -8140,9 +8140,6 @@ class CodeCacheHashTable: public HashTable<CodeCacheHashTable,
DECLARE_CAST(CodeCacheHashTable)
// Initial size of the fixed array backing the hash table.
static const int kInitialSize = 16;
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(CodeCacheHashTable);
};
......
......@@ -93,6 +93,7 @@ executable("cctest") {
"test-bignum.cc",
"test-bit-vector.cc",
"test-circular-queue.cc",
"test-code-cache.cc",
"test-compiler.cc",
"test-constantpool.cc",
"test-conversions.cc",
......
......@@ -128,6 +128,7 @@
'test-bignum-dtoa.cc',
'test-bit-vector.cc',
'test-circular-queue.cc',
'test-code-cache.cc',
'test-compiler.cc',
'test-constantpool.cc',
'test-conversions.cc',
......
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/v8.h"
#include "src/list.h"
#include "src/objects.h"
#include "test/cctest/cctest.h"
namespace v8 {
namespace internal {
namespace {
static Handle<Code> GetDummyCode(Isolate* isolate) {
CodeDesc desc = {nullptr, // buffer
0, // buffer_size
0, // instr_size
0, // reloc_size
0, // constant_pool_size
nullptr}; // origin
Code::Flags flags = Code::ComputeFlags(Code::LOAD_IC, MONOMORPHIC,
kNoExtraICState, kCacheOnReceiver);
Handle<Code> self_ref;
return isolate->factory()->NewCode(desc, flags, self_ref);
}
} // namespace
TEST(CodeCache) {
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory();
HandleScope handle_scope(isolate);
Handle<Map> map =
factory->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize, FAST_ELEMENTS);
// This number should be large enough to cause the code cache to use its
// hash table storage format.
static const int kEntries = 150;
// Prepare name/code pairs.
List<Handle<Name>> names(kEntries);
List<Handle<Code>> codes(kEntries);
for (int i = 0; i < kEntries; i++) {
names.Add(isolate->factory()->NewSymbol());
codes.Add(GetDummyCode(isolate));
}
Handle<Name> bad_name = isolate->factory()->NewSymbol();
Code::Flags bad_flags = Code::ComputeFlags(
Code::LOAD_IC, MONOMORPHIC, kNoExtraICState, kCacheOnPrototype);
DCHECK(bad_flags != codes[0]->flags());
// Cache name/code pairs.
for (int i = 0; i < kEntries; i++) {
Handle<Name> name = names.at(i);
Handle<Code> code = codes.at(i);
Map::UpdateCodeCache(map, name, code);
CHECK_EQ(*code, map->LookupInCodeCache(*name, code->flags()));
CHECK_NULL(map->LookupInCodeCache(*name, bad_flags));
}
CHECK_NULL(map->LookupInCodeCache(*bad_name, bad_flags));
// Check that lookup works not only right after storing.
for (int i = 0; i < kEntries; i++) {
Handle<Name> name = names.at(i);
Handle<Code> code = codes.at(i);
CHECK_EQ(*code, map->LookupInCodeCache(*name, code->flags()));
}
}
} // namespace internal
} // namespace v8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment