/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// There are two kinds of atoms handled by this module. // // - Dynamic: the atom itself is heap allocated, as is the char buffer it // points to. |gAtomTable| holds weak references to dynamic atoms. When the // refcount of a dynamic atom drops to zero, we increment a static counter. // When that counter reaches a certain threshold, we iterate over the atom // table, removing and deleting dynamic atoms with refcount zero. This allows // us to avoid acquiring the atom table lock during normal refcounting. // // - Static: both the atom and its chars are statically allocated and // immutable, so it ignores all AddRef/Release calls. // // Note that gAtomTable is used on multiple threads, and has internal // synchronization.
// gUnusedAtomCount is incremented when an atom loses its last reference // (and thus turned into unused state), and decremented when an unused // atom gets a reference again. The atom table relies on this value to // schedule GC. This value can temporarily go below zero when multiple // threads are operating the same atom, so it has to be signed so that // we wouldn't use overflow value for comparison. // See nsAtom::AddRef() and nsAtom::Release(). // This atomic can be accessed during the GC and other places where recorded // events are not allowed, so its value is not preserved when recording or // replaying.
Atomic<int32_t, ReleaseAcquire> nsDynamicAtom::gUnusedAtomCount;
void nsAtom::ToString(nsAString& aString) const { // See the comment on |mString|'s declaration. if (IsStatic()) { // AssignLiteral() lets us assign without copying. This isn't a string // literal, but it's a static atom and thus has an unbounded lifetime, // which is what's important.
aString.AssignLiteral(AsStatic()->String(), mLength);
} else {
aString.Assign(AsDynamic()->StringBuffer(), mLength);
}
}
void nsAtom::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
AtomsSizes& aSizes) const { // Static atoms are in static memory, and so are not measured here. if (IsDynamic()) {
aSizes.mDynamicAtoms += aMallocSizeOf(this);
}
}
struct AtomTableEntry : public PLDHashEntryHdr { // These references are either to dynamic atoms, in which case they are // non-owning, or they are to static atoms, which aren't really refcounted. // See the comment at the top of this file for more details.
nsAtom* MOZ_NON_OWNING_REF mAtom;
};
// In order to reduce locking contention for concurrent atomization, we segment // the atom table into N subtables, each with a separate lock. If the hash // values we use to select the subtable are evenly distributed, this reduces the // probability of contention by a factor of N. See bug 1440824. // // NB: This is somewhat similar to the technique used by Java's // ConcurrentHashTable. class nsAtomSubTable { friendclass nsAtomTable;
mozilla::RWLock mLock;
PLDHashTable mTable;
nsAtomSubTable(); void GCLocked(GCKind aKind) MOZ_REQUIRES(mLock); void AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf,
AtomsSizes& aSizes)
MOZ_REQUIRES_SHARED(mLock);
// The outer atom table, which coordinates access to the inner array of // subtables. class nsAtomTable { public:
nsAtomSubTable& SelectSubTable(AtomTableKey& aKey); void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes); void GC(GCKind aKind);
already_AddRefed<nsAtom> Atomize(const nsAString& aUTF16String,
uint32_t aHash);
already_AddRefed<nsAtom> Atomize(const nsACString& aUTF8String);
already_AddRefed<nsAtom> AtomizeMainThread(const nsAString& aUTF16String);
nsStaticAtom* GetStaticAtom(const nsAString& aUTF16String); void RegisterStaticAtoms(const nsStaticAtom* aAtoms, size_t aAtomsLen);
// The result of this function may be imprecise if other threads are operating // on atoms concurrently. It's also slow, since it triggers a GC before // counting.
size_t RacySlowCount();
// This hash table op is a static member of this class so that it can take // advantage of |friend| declarations. staticvoid AtomTableClearEntry(PLDHashTable* aTable,
PLDHashEntryHdr* aEntry);
// We achieve measurable reduction in locking contention in parallel CSS // parsing by increasing the number of subtables up to 128. This has been // measured to have neglible impact on the performance of initialization, GC, // and shutdown. // // Another important consideration is memory, since we're adding fixed // overhead per content process, which we try to avoid. Measuring a // mostly-empty page [1] with various numbers of subtables, we get the // following deep sizes for the atom table: // 1 subtable: 278K // 8 subtables: 279K // 16 subtables: 282K // 64 subtables: 286K // 128 subtables: 290K // // So 128 subtables costs us 12K relative to a single table, and 4K relative // to 64 subtables. Conversely, measuring parallel (6 thread) CSS parsing on // tp6-facebook, a single table provides ~150ms of locking overhead per // thread, 64 subtables provides ~2-3ms of overhead, and 128 subtables // provides <1ms. And so while either 64 or 128 subtables would probably be // acceptable, achieving a measurable reduction in contention for 4k of fixed // memory overhead is probably worth it. // // [1] The numbers will look different for content processes with complex // pages loaded, but in those cases the actual atoms will dominate memory // usage and the overhead of extra tables will be negligible. We're mostly // interested in the fixed cost for nearly-empty content processes.
constexpr static size_t kNumSubTables = 512; // Must be power of two.
// The atom table very quickly gets 10,000+ entries in it (or even 100,000+). // But choosing the best initial subtable length has some subtleties: we add // ~2700 static atoms at start-up, and then we start adding and removing // dynamic atoms. If we make the tables too big to start with, when the first // dynamic atom gets removed from a given table the load factor will be < 25% // and we will shrink it. // // So we first make the simplifying assumption that the atoms are more or less // evenly-distributed across the subtables (which is the case empirically). // Then, we take the total atom count when the first dynamic atom is removed // (~2700), divide that across the N subtables, and the largest capacity that // will allow each subtable to be > 25% full with that count. // // So want an initial subtable capacity less than (2700 / N) * 4 = 10800 / N. // Rounding down to the nearest power of two gives us 8192 / N. Since the // capacity is double the initial length, we end up with (4096 / N) per // subtable.
constexpr static size_t kInitialSubTableSize = 4096 / kNumSubTables;
nsAtomSubTable& nsAtomTable::SelectSubTable(AtomTableKey& aKey) { // There are a few considerations around how we select subtables. // // First, we want entries to be evenly distributed across the subtables. This // can be achieved by using any bits in the hash key, assuming the key itself // is evenly-distributed. Empirical measurements indicate that this method // produces a roughly-even distribution across subtables. // // Second, we want to use the hash bits that are least likely to influence an // entry's position within the subtable. If we used the exact same bits used // by the subtables, then each subtable would compute the same position for // every entry it observes, leading to pessimal performance. In this case, // we're using PLDHashTable, whose primary hash function uses the N leftmost // bits of the hash value (where N is the log2 capacity of the table). This // means we should prefer the rightmost bits here. // // Note that the below is equivalent to mHash % kNumSubTables, a replacement // which an optimizing compiler should make, but let's avoid any doubt.
static_assert((kNumSubTables & (kNumSubTables - 1)) == 0, "must be power of two"); return mSubTables[aKey.mHash & (kNumSubTables - 1)];
}
// Note that this is effectively an incremental GC, since only one subtable // is locked at a time. for (auto& table : mSubTables) {
AutoWriteLock lock(table.mLock);
table.GCLocked(aKind);
}
// We would like to assert that gUnusedAtomCount matches the number of atoms // we found in the table which we removed. However, there are two problems // with this: // * We have multiple subtables, each with their own lock. For optimal // performance we only want to hold one lock at a time, but this means // that atoms can be added and removed between GC slices. // * Even if we held all the locks and performed all GC slices atomically, // the locks are not acquired for AddRef() and Release() calls. This means // we might see a gUnusedAtomCount value in between, say, AddRef() // incrementing mRefCnt and it decrementing gUnusedAtomCount. // // So, we don't bother asserting that there are no unused atoms at the end of // a regular GC. But we can (and do) assert this just after the last GC at // shutdown. // // Note that, barring refcounting bugs, an atom can only go from a zero // refcount to a non-zero refcount while the atom table lock is held, so // so we won't try to resurrect a zero refcount atom while trying to delete // it.
size_t nsAtomTable::RacySlowCount() { // Trigger a GC so that the result is deterministic modulo other threads.
GC(GCKind::RegularOperation);
size_t count = 0; for (auto& table : mSubTables) {
AutoReadLock lock(table.mLock);
count += table.mTable.EntryCount();
}
// We register static atoms immediately so they're available for use as early // as possible.
gAtomTable = new nsAtomTable();
gAtomTable->RegisterStaticAtoms(nsGkAtoms::sAtoms, nsGkAtoms::sAtomsLen);
gStaticAtomsDone = true;
}
#ifdef NS_FREE_PERMANENT_DATA // Do a final GC to satisfy leak checking. We skip this step in release // builds.
gAtomTable->GC(GCKind::Shutdown); #endif
void nsAtomTable::RegisterStaticAtoms(const nsStaticAtom* aAtoms,
size_t aAtomsLen) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_RELEASE_ASSERT(!gStaticAtomsDone, "Static atom insertion is finished!");
for (uint32_t i = 0; i < aAtomsLen; ++i) { const nsStaticAtom* atom = &aAtoms[i];
MOZ_ASSERT(IsAsciiNullTerminated(atom->String()));
MOZ_ASSERT(NS_strlen(atom->String()) == atom->GetLength());
MOZ_ASSERT(atom->IsAsciiLowercase() ==
::IsAsciiLowercase(atom->String(), atom->GetLength()));
// This assertion ensures the static atom's precomputed hash value matches // what would be computed by mozilla::HashString(aStr), which is what we use // when atomizing strings. We compute this hash in Atom.py.
MOZ_ASSERT(HashString(atom->String()) == atom->hash());
AtomTableKey key(atom);
nsAtomSubTable& table = SelectSubTable(key);
AutoWriteLock lock(table.mLock);
AtomTableEntry* he = table.Add(key); if (he->mAtom) { // There are two ways we could get here. // - Register two static atoms with the same string. // - Create a dynamic atom and then register a static atom with the same // string while the dynamic atom is alive. // Both cases can cause subtle bugs, and are disallowed. We're // programming in C++ here, not Smalltalk.
nsAutoCString name;
he->mAtom->ToUTF8String(name);
MOZ_CRASH_UNSAFE_PRINTF("Atom for '%s' already exists", name.get());
}
he->mAtom = const_cast<nsStaticAtom*>(atom);
}
}
void ToLowerCaseASCII(RefPtr<nsAtom>& aAtom) { // Assume the common case is that the atom is already ASCII lowercase. if (aAtom->IsAsciiLowercase()) { return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.