зеркало из 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);
|
||||
}
|
||||
|
||||
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 };
|
||||
|
||||
template <KeysKind Kind>
|
||||
static PlainObject* NewPlainObjectWithProperties(JSContext* cx,
|
||||
IdValuePair* properties,
|
||||
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);
|
||||
Rooted<PlainObject*> obj(cx, NewPlainObjectWithAllocKind(cx, allocKind));
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (nproperties == 0) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
Rooted<PropertyKey> key(cx);
|
||||
Rooted<Value> value(cx);
|
||||
bool canCache = true;
|
||||
|
||||
for (size_t i = 0; i < nproperties; i++) {
|
||||
key = properties[i].id;
|
||||
|
@ -219,6 +279,7 @@ static PlainObject* NewPlainObjectWithProperties(JSContext* cx,
|
|||
// just fall back to NativeDefineDataProperty.
|
||||
if constexpr (Kind == KeysKind::Unknown) {
|
||||
if (MOZ_UNLIKELY(key.isInt())) {
|
||||
canCache = false;
|
||||
if (!NativeDefineDataProperty(cx, obj, key, value, JSPROP_ENUMERATE)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -235,6 +296,7 @@ static PlainObject* NewPlainObjectWithProperties(JSContext* cx,
|
|||
} else {
|
||||
mozilla::Maybe<PropertyInfo> prop = obj->lookup(cx, key);
|
||||
if (MOZ_UNLIKELY(prop)) {
|
||||
canCache = false;
|
||||
MOZ_ASSERT(prop->isDataProperty());
|
||||
obj->setSlot(prop->slot(), value);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -395,6 +395,7 @@ void Realm::fixupAfterMovingGC(JSTracer* trc) {
|
|||
void Realm::purge() {
|
||||
dtoaCache.purge();
|
||||
newProxyCache.purge();
|
||||
newPlainObjectWithPropsCache.purge();
|
||||
objects_.iteratorCache.clearAndCompact();
|
||||
arraySpeciesLookup.purge();
|
||||
promiseLookup.purge();
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef vm_Realm_h
|
||||
#define vm_Realm_h
|
||||
|
||||
#include "mozilla/Array.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
@ -127,6 +128,25 @@ class NewProxyCache {
|
|||
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
|
||||
//
|
||||
// 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::NewProxyCache newProxyCache;
|
||||
js::NewPlainObjectWithPropsCache newPlainObjectWithPropsCache;
|
||||
js::ArraySpeciesLookup arraySpeciesLookup;
|
||||
js::PromiseLookup promiseLookup;
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче