зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1777222 part 5 - Add a small cache to optimize NewPlainObjectWithProperties. r=jonco
With TI we had a cache for this but we got rid of it when we removed TI. This patch adds back a simpler version of it that caches the four most recently created shapes. This cache has a good hit rate on the web and on Kraken's json-parse-financial test. Differential Revision: https://phabricator.services.mozilla.com/D150558
This commit is contained in:
Родитель
144d3f7cff
Коммит
40ed12b21c
|
@ -0,0 +1,35 @@
|
||||||
|
// Array includes objects with duplicate keys and integer keys.
|
||||||
|
let json = `[
|
||||||
|
{"x1": 1},
|
||||||
|
{"x2": 2},
|
||||||
|
{"x3": 3},
|
||||||
|
{"x1": 1, "y": 0},
|
||||||
|
{"x2": 1, "y": 0},
|
||||||
|
{"x3": 1, "y": 0},
|
||||||
|
{"x1": 1, "x1": 2, "y": 0},
|
||||||
|
{"x1": 1, "x1": 2, "y": 0},
|
||||||
|
{"x1": 1, "x1": 2, "y": 0},
|
||||||
|
{"0": 1, "x1": 1},
|
||||||
|
{"0": 1, "0": 2, "x1": 1},
|
||||||
|
{"0": 1, "0": 2, "x1": 1},
|
||||||
|
{"__proto__": 1},
|
||||||
|
{"__proto__": 2}
|
||||||
|
]`;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
let res = JSON.parse(json);
|
||||||
|
assertEq(JSON.stringify(res),
|
||||||
|
`[{"x1":1},` +
|
||||||
|
`{"x2":2},` +
|
||||||
|
`{"x3":3},` +
|
||||||
|
`{"x1":1,"y":0},` +
|
||||||
|
`{"x2":1,"y":0},` +
|
||||||
|
`{"x3":1,"y":0},` +
|
||||||
|
`{"x1":2,"y":0},` +
|
||||||
|
`{"x1":2,"y":0},` +
|
||||||
|
`{"x1":2,"y":0},` +
|
||||||
|
`{"0":1,"x1":1},` +
|
||||||
|
`{"0":2,"x1":1},` +
|
||||||
|
`{"0":2,"x1":1},` +
|
||||||
|
`{"__proto__":1},` +
|
||||||
|
`{"__proto__":2}]`);
|
||||||
|
}
|
|
@ -196,20 +196,80 @@ PlainObject* js::NewPlainObjectWithProtoAndAllocKind(JSContext* cx,
|
||||||
return PlainObject::createWithShape(cx, shape, allocKind, newKind);
|
return PlainObject::createWithShape(cx, shape, allocKind, newKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void js::NewPlainObjectWithPropsCache::add(Shape* shape) {
|
||||||
|
MOZ_ASSERT(shape);
|
||||||
|
MOZ_ASSERT(shape->slotSpan() > 0);
|
||||||
|
for (size_t i = NumEntries - 1; i > 0; i--) {
|
||||||
|
entries_[i] = entries_[i - 1];
|
||||||
|
}
|
||||||
|
entries_[0] = shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ShapeMatches(IdValuePair* properties, size_t nproperties,
|
||||||
|
Shape* shape) {
|
||||||
|
if (shape->slotSpan() != nproperties) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ShapePropertyIter<NoGC> iter(shape);
|
||||||
|
for (size_t i = nproperties; i > 0; i--) {
|
||||||
|
MOZ_ASSERT(iter->isDataProperty());
|
||||||
|
MOZ_ASSERT(iter->flags() == PropertyFlags::defaultDataPropFlags);
|
||||||
|
if (properties[i - 1].id != iter->key()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
MOZ_ASSERT(iter.done());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape* js::NewPlainObjectWithPropsCache::lookup(IdValuePair* properties,
|
||||||
|
size_t nproperties) const {
|
||||||
|
for (size_t i = 0; i < NumEntries; i++) {
|
||||||
|
Shape* shape = entries_[i];
|
||||||
|
if (shape && ShapeMatches(properties, nproperties, shape)) {
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
enum class KeysKind { UniqueNames, Unknown };
|
enum class KeysKind { UniqueNames, Unknown };
|
||||||
|
|
||||||
template <KeysKind Kind>
|
template <KeysKind Kind>
|
||||||
static PlainObject* NewPlainObjectWithProperties(JSContext* cx,
|
static PlainObject* NewPlainObjectWithProperties(JSContext* cx,
|
||||||
IdValuePair* properties,
|
IdValuePair* properties,
|
||||||
size_t nproperties) {
|
size_t nproperties) {
|
||||||
|
auto& cache = cx->realm()->newPlainObjectWithPropsCache;
|
||||||
|
|
||||||
|
// If we recently created an object with these properties, we can use that
|
||||||
|
// Shape directly.
|
||||||
|
if (Shape* shape = cache.lookup(properties, nproperties)) {
|
||||||
|
Rooted<Shape*> shapeRoot(cx, shape);
|
||||||
|
PlainObject* obj = PlainObject::createWithShape(cx, shapeRoot);
|
||||||
|
if (!obj) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
MOZ_ASSERT(obj->slotSpan() == nproperties);
|
||||||
|
for (size_t i = 0; i < nproperties; i++) {
|
||||||
|
obj->initSlot(i, properties[i].value);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
|
gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
|
||||||
Rooted<PlainObject*> obj(cx, NewPlainObjectWithAllocKind(cx, allocKind));
|
Rooted<PlainObject*> obj(cx, NewPlainObjectWithAllocKind(cx, allocKind));
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nproperties == 0) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
Rooted<PropertyKey> key(cx);
|
Rooted<PropertyKey> key(cx);
|
||||||
Rooted<Value> value(cx);
|
Rooted<Value> value(cx);
|
||||||
|
bool canCache = true;
|
||||||
|
|
||||||
for (size_t i = 0; i < nproperties; i++) {
|
for (size_t i = 0; i < nproperties; i++) {
|
||||||
key = properties[i].id;
|
key = properties[i].id;
|
||||||
|
@ -219,6 +279,7 @@ static PlainObject* NewPlainObjectWithProperties(JSContext* cx,
|
||||||
// just fall back to NativeDefineDataProperty.
|
// just fall back to NativeDefineDataProperty.
|
||||||
if constexpr (Kind == KeysKind::Unknown) {
|
if constexpr (Kind == KeysKind::Unknown) {
|
||||||
if (MOZ_UNLIKELY(key.isInt())) {
|
if (MOZ_UNLIKELY(key.isInt())) {
|
||||||
|
canCache = false;
|
||||||
if (!NativeDefineDataProperty(cx, obj, key, value, JSPROP_ENUMERATE)) {
|
if (!NativeDefineDataProperty(cx, obj, key, value, JSPROP_ENUMERATE)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -235,6 +296,7 @@ static PlainObject* NewPlainObjectWithProperties(JSContext* cx,
|
||||||
} else {
|
} else {
|
||||||
mozilla::Maybe<PropertyInfo> prop = obj->lookup(cx, key);
|
mozilla::Maybe<PropertyInfo> prop = obj->lookup(cx, key);
|
||||||
if (MOZ_UNLIKELY(prop)) {
|
if (MOZ_UNLIKELY(prop)) {
|
||||||
|
canCache = false;
|
||||||
MOZ_ASSERT(prop->isDataProperty());
|
MOZ_ASSERT(prop->isDataProperty());
|
||||||
obj->setSlot(prop->slot(), value);
|
obj->setSlot(prop->slot(), value);
|
||||||
continue;
|
continue;
|
||||||
|
@ -246,6 +308,12 @@ static PlainObject* NewPlainObjectWithProperties(JSContext* cx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canCache && !obj->inDictionaryMode()) {
|
||||||
|
MOZ_ASSERT(!obj->isIndexed());
|
||||||
|
MOZ_ASSERT(obj->slotSpan() == nproperties);
|
||||||
|
cache.add(obj->shape());
|
||||||
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -395,6 +395,7 @@ void Realm::fixupAfterMovingGC(JSTracer* trc) {
|
||||||
void Realm::purge() {
|
void Realm::purge() {
|
||||||
dtoaCache.purge();
|
dtoaCache.purge();
|
||||||
newProxyCache.purge();
|
newProxyCache.purge();
|
||||||
|
newPlainObjectWithPropsCache.purge();
|
||||||
objects_.iteratorCache.clearAndCompact();
|
objects_.iteratorCache.clearAndCompact();
|
||||||
arraySpeciesLookup.purge();
|
arraySpeciesLookup.purge();
|
||||||
promiseLookup.purge();
|
promiseLookup.purge();
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#ifndef vm_Realm_h
|
#ifndef vm_Realm_h
|
||||||
#define vm_Realm_h
|
#define vm_Realm_h
|
||||||
|
|
||||||
|
#include "mozilla/Array.h"
|
||||||
#include "mozilla/Atomics.h"
|
#include "mozilla/Atomics.h"
|
||||||
#include "mozilla/LinkedList.h"
|
#include "mozilla/LinkedList.h"
|
||||||
#include "mozilla/Maybe.h"
|
#include "mozilla/Maybe.h"
|
||||||
|
@ -127,6 +128,25 @@ class NewProxyCache {
|
||||||
void purge() { entries_.reset(); }
|
void purge() { entries_.reset(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cache for NewPlainObjectWithProperties. When the list of properties matches
|
||||||
|
// a recently created object's shape, we can use this shape directly.
|
||||||
|
class NewPlainObjectWithPropsCache {
|
||||||
|
static const size_t NumEntries = 4;
|
||||||
|
mozilla::Array<Shape*, NumEntries> entries_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
NewPlainObjectWithPropsCache() { purge(); }
|
||||||
|
|
||||||
|
Shape* lookup(IdValuePair* properties, size_t nproperties) const;
|
||||||
|
void add(Shape* shape);
|
||||||
|
|
||||||
|
void purge() {
|
||||||
|
for (size_t i = 0; i < NumEntries; i++) {
|
||||||
|
entries_[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// [SMDOC] Object MetadataBuilder API
|
// [SMDOC] Object MetadataBuilder API
|
||||||
//
|
//
|
||||||
// We must ensure that all newly allocated JSObjects get their metadata
|
// We must ensure that all newly allocated JSObjects get their metadata
|
||||||
|
@ -404,6 +424,7 @@ class JS::Realm : public JS::shadow::Realm {
|
||||||
|
|
||||||
js::DtoaCache dtoaCache;
|
js::DtoaCache dtoaCache;
|
||||||
js::NewProxyCache newProxyCache;
|
js::NewProxyCache newProxyCache;
|
||||||
|
js::NewPlainObjectWithPropsCache newPlainObjectWithPropsCache;
|
||||||
js::ArraySpeciesLookup arraySpeciesLookup;
|
js::ArraySpeciesLookup arraySpeciesLookup;
|
||||||
js::PromiseLookup promiseLookup;
|
js::PromiseLookup promiseLookup;
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче