mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-20 15:27:32 +09:00
Implement class selector fast path and add related tests for performance optimization
This commit is contained in:
parent
dfe7760702
commit
80cfaf0891
8 changed files with 233 additions and 2 deletions
38
js/src/devtools/speedometer21-json-bench.js
Normal file
38
js/src/devtools/speedometer21-json-bench.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Focused JSON workloads motivated by Speedometer 2.1's in-memory TodoMVC store.
|
||||
// Run with a JS shell, or xpcshell -f. Lower times are better. This does not
|
||||
// measure Speedometer's DOM, layout, event dispatch, or overall suite score.
|
||||
(function() {
|
||||
var iterations = 2000;
|
||||
var checksum = 0;
|
||||
function measure(name, records) {
|
||||
var text = JSON.stringify(records);
|
||||
var samples = [];
|
||||
for (var i = 0; i < 100; i++)
|
||||
checksum += JSON.parse(text).length;
|
||||
for (var sample = 0; sample < 7; sample++) {
|
||||
if (typeof gc === 'function')
|
||||
gc();
|
||||
var start = Date.now();
|
||||
for (var i = 0; i < iterations; i++)
|
||||
checksum += JSON.parse(text).length;
|
||||
samples.push(Date.now() - start);
|
||||
}
|
||||
samples.sort(function(a, b) { return a - b; });
|
||||
print(name + ': median=' + samples[3] + ' ms; samples=' + samples.join(','));
|
||||
}
|
||||
var todos = [], unicode = [], collisions = [], unique = [];
|
||||
for (var i = 0; i < 100; i++) {
|
||||
todos.push({ id: i, title: 'Something to do ' + i, completed: false });
|
||||
unicode.push({ '\u0101name': i, '\u03bbvalue': 'value', '\u4e2d': false });
|
||||
collisions.push({ item: i, identifier: i + 1, index: i + 2 });
|
||||
var record = {};
|
||||
record['uniqueName' + i] = i;
|
||||
unique.push(record);
|
||||
}
|
||||
measure('TodoMVC-shaped records', todos);
|
||||
measure('two-byte names', unicode);
|
||||
measure('cache collisions', collisions);
|
||||
measure('unique names', unique);
|
||||
measure('small parse', [todos[0]]);
|
||||
print('checksum=' + checksum);
|
||||
})();
|
||||
43
js/src/jit-test/tests/basic/json-property-name-cache.js
Normal file
43
js/src/jit-test/tests/basic/json-property-name-cache.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Repeated names, collisions, encodings, and escaped names must all agree.
|
||||
var keys = ['id', 'title', 'completed', 'items', 'item', '', 'identifier',
|
||||
'i', 'a', 'same', 'samesize', '\u00e9', '\u0101', '\ud800',
|
||||
'quote"key', 'slash\\key', 'line\nkey', '__proto__'];
|
||||
var records = [];
|
||||
for (var i = 0; i < 100; i++) {
|
||||
var record = Object.create(null);
|
||||
for (var k = 0; k < keys.length; k++)
|
||||
record[keys[k]] = i + k;
|
||||
records.push(record);
|
||||
}
|
||||
var text = JSON.stringify(records);
|
||||
for (var repeat = 0; repeat < 30; repeat++) {
|
||||
var parsed = JSON.parse(text);
|
||||
assertEq(parsed.length, records.length);
|
||||
for (var i = 0; i < parsed.length; i++) {
|
||||
for (var k = 0; k < keys.length; k++)
|
||||
assertEq(parsed[i][keys[k]], records[i][keys[k]]);
|
||||
}
|
||||
}
|
||||
assertEq(JSON.parse('[{"title":1},{"titleLonger":2}]')[1].titleLonger, 2);
|
||||
assertEq(JSON.parse('[{"titleLonger":1},{"title":2}]')[1].title, 2);
|
||||
assertEq(JSON.parse('[{"title":1},{"t\\u0069tle":2}]')[1].title, 2);
|
||||
assertEq(JSON.parse('{"id":1,"id":2}').id, 2);
|
||||
assertEq(JSON.parse('{"id":1}', function(key, value) {
|
||||
return key === 'id' ? JSON.parse('{"id":2}').id : value;
|
||||
}).id, 2);
|
||||
|
||||
for (var bad of ['[{"title":1},{"title', '[{"title":1},{"title"',
|
||||
'[{"title":1},{"titleX":}]', '[{"title":1},{"ti\ntle":2}]']) {
|
||||
var threw = false;
|
||||
try { JSON.parse(bad); } catch (e) { threw = e instanceof SyntaxError; }
|
||||
assertEq(threw, true);
|
||||
}
|
||||
|
||||
// Keep cached atoms alive across allocations made while parsing later values.
|
||||
var large = '[{"uncommonPropertyForGC":0},' +
|
||||
'{"other":[' + new Array(20000).fill('"allocation"').join(',') + ']},' +
|
||||
'{"uncommonPropertyForGC":1}]';
|
||||
for (var i = 0; i < 3; i++) {
|
||||
gc();
|
||||
assertEq(JSON.parse(large)[2].uncommonPropertyForGC, 1);
|
||||
}
|
||||
|
|
@ -43,6 +43,10 @@ JSONParserBase::~JSONParserBase()
|
|||
void
|
||||
JSONParserBase::trace(JSTracer* trc)
|
||||
{
|
||||
for (auto& atom : propertyNameCache) {
|
||||
if (atom)
|
||||
TraceRoot(trc, &atom, "JSONParser cached property name");
|
||||
}
|
||||
for (size_t i = 0; i < stack.length(); i++) {
|
||||
if (stack[i].state == FinishArrayElement) {
|
||||
ElementVector& elements = stack[i].elements();
|
||||
|
|
@ -123,6 +127,24 @@ JSONParser<CharT>::readString()
|
|||
return token(Error);
|
||||
}
|
||||
|
||||
// Arrays of records repeatedly use the same property names. Verify the
|
||||
// entire name and closing quote before skipping scanning and atomization.
|
||||
// Only the unescaped path below populates this cache.
|
||||
if (ST == JSONParser::PropertyName && *current != '"') {
|
||||
JSAtom* atom = propertyNameCache[size_t(*current) % PropertyNameCacheSize];
|
||||
if (atom && size_t(end - current) > atom->length() &&
|
||||
current[atom->length()] == '"') {
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
bool matches = atom->hasLatin1Chars()
|
||||
? EqualChars(atom->latin1Chars(nogc), current.get(), atom->length())
|
||||
: EqualChars(atom->twoByteChars(nogc), current.get(), atom->length());
|
||||
if (matches) {
|
||||
current += atom->length() + 1;
|
||||
return stringToken(atom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Optimization: if the source contains no escaped characters, create the
|
||||
* string directly from the source text.
|
||||
|
|
@ -137,6 +159,9 @@ JSONParser<CharT>::readString()
|
|||
: NewStringCopyN<CanGC>(cx, start.get(), length);
|
||||
if (!str)
|
||||
return token(OOM);
|
||||
if (ST == JSONParser::PropertyName && length) {
|
||||
propertyNameCache[size_t(*start) % PropertyNameCacheSize] = &str->asAtom();
|
||||
}
|
||||
return stringToken(str);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ class MOZ_STACK_CLASS JSONParserBase
|
|||
|
||||
const ErrorHandling errorHandling;
|
||||
|
||||
// Reuse unescaped property names within this parse. The cache is bounded
|
||||
// and traced with the parser, including during compacting GC.
|
||||
static const size_t PropertyNameCacheSize = 8;
|
||||
JSAtom* propertyNameCache[PropertyNameCacheSize];
|
||||
|
||||
enum Token { String, Number, True, False, Null,
|
||||
ArrayOpen, ArrayClose,
|
||||
ObjectOpen, ObjectClose,
|
||||
|
|
@ -109,6 +114,7 @@ class MOZ_STACK_CLASS JSONParserBase
|
|||
JSONParserBase(JSContext* cx, ErrorHandling errorHandling)
|
||||
: cx(cx),
|
||||
errorHandling(errorHandling),
|
||||
propertyNameCache{},
|
||||
stack(cx),
|
||||
freeElements(cx),
|
||||
freeProperties(cx)
|
||||
|
|
@ -129,8 +135,10 @@ class MOZ_STACK_CLASS JSONParserBase
|
|||
#ifdef DEBUG
|
||||
, lastToken(mozilla::Move(other.lastToken))
|
||||
#endif
|
||||
{}
|
||||
|
||||
{
|
||||
for (size_t i = 0; i < PropertyNameCacheSize; i++)
|
||||
propertyNameCache[i] = other.propertyNameCache[i];
|
||||
}
|
||||
|
||||
Value numberValue() const {
|
||||
MOZ_ASSERT(lastToken == Number);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue