Bug 1799628 - Cache most recent lookups in StringToAtomCache. r=jonco

`js::AtomizeString` is pretty hot and caching the most recent lookups helps avoid
slower hash table lookups in 30-65% of cases (> 60% on Speedometer 2).

Differential Revision: https://phabricator.services.mozilla.com/D161571
This commit is contained in:
Jan de Mooij 2022-11-08 13:56:53 +00:00
Родитель 977ed830c3
Коммит 7d52885c10
2 изменённых файлов: 53 добавлений и 12 удалений

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

@ -711,7 +711,7 @@ JSString* js::TenuringTracer::moveToTenured(JSString* src) {
if (src->inStringToAtomCache() && src->isDeduplicatable() &&
!src->hasBase()) {
JSLinearString* linear = &src->asLinear();
JSAtom* atom = runtime()->caches().stringToAtomCache.lookup(linear);
JSAtom* atom = runtime()->caches().stringToAtomCache.lookupInMap(linear);
MOZ_ASSERT(atom, "Why was the cache purged before minor GC?");
// Only deduplicate if both strings have the same encoding, to not confuse

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

@ -237,24 +237,34 @@ class MegamorphicCache {
};
// Cache for AtomizeString, mapping JSLinearString* to the corresponding
// JSAtom*. Also used by nursery GC to de-duplicate strings to atoms.
// Purged on minor and major GC.
// JSAtom*. The cache has two different optimizations:
//
// * The two most recent lookups are cached. This has a hit rate of 30-65% on
// typical web workloads.
//
// * For longer strings, there's also a JSLinearString* => JSAtom* HashMap,
// because hashing the string characters repeatedly can be slow.
// This map is also used by nursery GC to de-duplicate strings to atoms.
//
// This cache is purged on minor and major GC.
class StringToAtomCache {
using Map = HashMap<JSLinearString*, JSAtom*, PointerHasher<JSLinearString*>,
SystemAllocPolicy>;
Map map_;
struct LastEntry {
JSLinearString* string = nullptr;
JSAtom* atom = nullptr;
};
static constexpr size_t NumLastEntries = 2;
mozilla::Array<LastEntry, NumLastEntries> lastLookups_;
public:
// Don't use the cache for short strings. Hashing them is less expensive.
// Don't use the HashMap for short strings. Hashing them is less expensive.
static constexpr size_t MinStringLength = 30;
JSAtom* lookup(JSLinearString* s) {
MOZ_ASSERT(!s->isAtom());
if (!s->inStringToAtomCache()) {
MOZ_ASSERT(!map_.lookup(s));
return nullptr;
}
JSAtom* lookupInMap(JSLinearString* s) const {
MOZ_ASSERT(s->inStringToAtomCache());
MOZ_ASSERT(s->length() >= MinStringLength);
auto p = map_.lookup(s);
@ -263,8 +273,33 @@ class StringToAtomCache {
return atom;
}
MOZ_ALWAYS_INLINE JSAtom* lookup(JSLinearString* s) const {
MOZ_ASSERT(!s->isAtom());
for (const LastEntry& entry : lastLookups_) {
if (entry.string == s) {
MOZ_ASSERT(EqualStrings(s, entry.atom));
return entry.atom;
}
}
if (!s->inStringToAtomCache()) {
MOZ_ASSERT(!map_.lookup(s));
return nullptr;
}
return lookupInMap(s);
}
void maybePut(JSLinearString* s, JSAtom* atom) {
MOZ_ASSERT(!s->isAtom());
for (size_t i = NumLastEntries - 1; i > 0; i--) {
lastLookups_[i] = lastLookups_[i - 1];
}
lastLookups_[0].string = s;
lastLookups_[0].atom = atom;
if (s->length() < MinStringLength) {
return;
}
@ -274,7 +309,13 @@ class StringToAtomCache {
s->setInStringToAtomCache();
}
void purge() { map_.clearAndCompact(); }
void purge() {
map_.clearAndCompact();
for (LastEntry& entry : lastLookups_) {
entry.string = nullptr;
entry.atom = nullptr;
}
}
};
class RuntimeCaches {