Skip to content

Commit 4c63263

Browse files
committed
[dart2wasm] Avoid repeatedly allocating strings for the same json object keys
Most jsons have ascii strings as keys in json objects and very often those keys are highly repetitive. In previous CLs we started to compute the hash of the json object keys eagerly, even before allocating the string. We can take advantage of this now by using consulting a fixed-size interning cache. The cost of this cache is * Memory: It has max 512 entries and only contains one byte strings used as keys in json (keys are usually very small). * Lookup: Bitmask and lookup in array, length comparison (fails often if keys are not the same), plus byte comparison (may often suceeed) * Insert: Simply store into an array. The benefit is that we are very likely to allocate a lot less string objects for the keys. This will make data fit better in caches, will make string equality checks (in map lookups) more often hit the fast case (pointer equality) and reduce GC pressure. Change-Id: Id8ed3a972a267dd0201383f8f51ed82758bc0e63 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/409680 Reviewed-by: Ömer Ağacan <[email protected]> Commit-Queue: Martin Kustermann <[email protected]>
1 parent 572b501 commit 4c63263

File tree

1 file changed

+75
-13
lines changed

1 file changed

+75
-13
lines changed

sdk/lib/_internal/wasm/lib/convert_patch.dart

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dynamic _parseJson(
2727
String source,
2828
Object? Function(Object? key, Object? value)? reviver,
2929
) {
30+
_oneByteStringInternCache.fill(0, null, _oneByteStringInternCacheSize);
3031
final listener = _JsonListener(reviver);
3132
if (source is OneByteString) {
3233
final parser = _JsonOneByteStringParser(listener);
@@ -1644,13 +1645,7 @@ class _JsonOneByteStringParser extends _ChunkedJsonParserState
16441645
String getStringWithHash(int start, int end, int bits, int stringHash) {
16451646
final sourceArray = chunk.array;
16461647
final length = end - start;
1647-
final result = OneByteString.withLength(length);
1648-
for (int i = 0; i < length; ++i) {
1649-
result.array.write(i, sourceArray.readUnsigned(start++));
1650-
}
1651-
assert(result.hashCode.toWasmI32() == stringHash.toWasmI32());
1652-
setIdentityHashField(result, stringHash);
1653-
return result;
1648+
return _internOneByteStringFromI8(sourceArray, stringHash, start, length);
16541649
}
16551650

16561651
void beginString() {
@@ -1732,12 +1727,12 @@ class _JsonTwoByteStringParser extends _ChunkedJsonParserState
17321727

17331728
const asciiBits = 0x7f;
17341729
if (bits <= asciiBits) {
1735-
final result = OneByteString.withLength(length);
1736-
for (int i = 0; i < length; ++i) {
1737-
result.array.write(i, sourceArray.readUnsigned(start++));
1738-
}
1739-
setIdentityHashField(result, stringHash);
1740-
return result;
1730+
return _internOneByteStringFromI16(
1731+
sourceArray,
1732+
stringHash,
1733+
start,
1734+
length,
1735+
);
17411736
}
17421737

17431738
final result = TwoByteString.withLength(length);
@@ -1787,6 +1782,73 @@ class _JsonTwoByteStringParser extends _ChunkedJsonParserState
17871782
}
17881783
}
17891784

1785+
const int _oneByteStringInternCacheSize = 512;
1786+
final WasmArray<OneByteString?> _oneByteStringInternCache =
1787+
WasmArray<OneByteString?>(_oneByteStringInternCacheSize);
1788+
OneByteString _internOneByteStringFromI8(
1789+
WasmArray<WasmI8> array,
1790+
int stringHash,
1791+
int offset,
1792+
int length,
1793+
) {
1794+
final int index = stringHash & (_oneByteStringInternCacheSize - 1);
1795+
final existing = _oneByteStringInternCache[index];
1796+
1797+
insert:
1798+
{
1799+
if (existing != null) {
1800+
if (existing.length == length) {
1801+
final existingArray = existing.array;
1802+
for (int start = offset, i = 0; i < length; ++i, start++) {
1803+
if (existingArray.readUnsigned(i) != array.readUnsigned(start)) {
1804+
break insert;
1805+
}
1806+
}
1807+
return existing;
1808+
}
1809+
}
1810+
}
1811+
1812+
final result = OneByteString.withLength(length);
1813+
result.array.copy(0, array, offset, length);
1814+
setIdentityHashField(result, stringHash);
1815+
_oneByteStringInternCache[index] = result;
1816+
return result;
1817+
}
1818+
1819+
OneByteString _internOneByteStringFromI16(
1820+
WasmArray<WasmI16> array,
1821+
int stringHash,
1822+
int offset,
1823+
int length,
1824+
) {
1825+
final int index = stringHash & (_oneByteStringInternCacheSize - 1);
1826+
final existing = _oneByteStringInternCache[index];
1827+
1828+
insert:
1829+
{
1830+
if (existing != null) {
1831+
if (existing.length == length) {
1832+
final existingArray = existing.array;
1833+
for (int start = offset, i = 0; i < length; ++i, start++) {
1834+
if (existingArray.readUnsigned(i) != array.readUnsigned(start)) {
1835+
break insert;
1836+
}
1837+
}
1838+
return existing;
1839+
}
1840+
}
1841+
}
1842+
1843+
final result = OneByteString.withLength(length);
1844+
for (int start = offset, i = 0; i < length; ++i, start++) {
1845+
result.array.write(i, array.readUnsigned(start));
1846+
}
1847+
setIdentityHashField(result, stringHash);
1848+
_oneByteStringInternCache[index] = result;
1849+
return result;
1850+
}
1851+
17901852
@patch
17911853
class JsonDecoder {
17921854
@patch

0 commit comments

Comments
 (0)