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:
Jan de Mooij 2022-07-04 08:26:34 +00:00
Родитель 144d3f7cff
Коммит 40ed12b21c
4 изменённых файлов: 125 добавлений и 0 удалений

Просмотреть файл

@ -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;