зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1275755 - Use a GC scheme to free unused atoms. r=froydnj
This commit is contained in:
Родитель
95caf3be29
Коммит
2c597c5a3d
|
@ -27,9 +27,11 @@
|
||||||
// There are two kinds of atoms handled by this module.
|
// There are two kinds of atoms handled by this module.
|
||||||
//
|
//
|
||||||
// - DynamicAtom: the atom itself is heap allocated, as is the nsStringBuffer it
|
// - DynamicAtom: the atom itself is heap allocated, as is the nsStringBuffer it
|
||||||
// points to. |gAtomTable| holds weak references to them DynamicAtoms, and
|
// points to. |gAtomTable| holds weak references to them DynamicAtoms. When
|
||||||
// when they are destroyed (due to their refcount reaching zero) they remove
|
// the refcount of a DynamicAtom drops to zero, we increment a static counter.
|
||||||
// themselves from |gAtomTable|.
|
// When that counter reaches a certain threshold, we iterate over the atom
|
||||||
|
// table, removing and deleting DynamicAtoms with refcount zero. This allows
|
||||||
|
// us to avoid acquiring the atom table lock during normal refcounting.
|
||||||
//
|
//
|
||||||
// - StaticAtom: the atom itself is heap allocated, but it points to a static
|
// - StaticAtom: the atom itself is heap allocated, but it points to a static
|
||||||
// nsStringBuffer. |gAtomTable| effectively owns StaticAtoms, because such
|
// nsStringBuffer. |gAtomTable| effectively owns StaticAtoms, because such
|
||||||
|
@ -63,10 +65,22 @@ class CheckStaticAtomSizes
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
|
static Atomic<uint32_t, ReleaseAcquire> gUnusedAtomCount(0);
|
||||||
|
|
||||||
class DynamicAtom final : public nsIAtom
|
class DynamicAtom final : public nsIAtom
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static already_AddRefed<DynamicAtom> Create(const nsAString& aString, uint32_t aHash)
|
||||||
|
{
|
||||||
|
// The refcount is appropriately initialized in the constructor.
|
||||||
|
return dont_AddRef(new DynamicAtom(aString, aHash));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GCAtomTable();
|
||||||
|
|
||||||
|
private:
|
||||||
DynamicAtom(const nsAString& aString, uint32_t aHash)
|
DynamicAtom(const nsAString& aString, uint32_t aHash)
|
||||||
|
: mRefCnt(1)
|
||||||
{
|
{
|
||||||
mLength = aString.Length();
|
mLength = aString.Length();
|
||||||
mIsStatic = false;
|
mIsStatic = false;
|
||||||
|
@ -100,19 +114,16 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// We don't need a virtual destructor because we always delete via a
|
// We don't need a virtual destructor because we always delete via a
|
||||||
// DynamicAtom* pointer (in Release(), defined via NS_IMPL_ISUPPORTS), not an
|
// DynamicAtom* pointer (in GCAtomTable()), not an nsIAtom* pointer.
|
||||||
// nsIAtom* pointer.
|
|
||||||
~DynamicAtom();
|
~DynamicAtom();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NS_DECL_ISUPPORTS
|
NS_DECL_THREADSAFE_ISUPPORTS
|
||||||
NS_DECL_NSIATOM
|
NS_DECL_NSIATOM
|
||||||
|
|
||||||
void TransmuteToStatic(nsStringBuffer* aStringBuffer);
|
void TransmuteToStatic(nsStringBuffer* aStringBuffer);
|
||||||
};
|
};
|
||||||
|
|
||||||
NS_IMPL_ISUPPORTS(DynamicAtom, nsIAtom)
|
|
||||||
|
|
||||||
class StaticAtom final : public nsIAtom
|
class StaticAtom final : public nsIAtom
|
||||||
{
|
{
|
||||||
// This is the function that calls the private constructor.
|
// This is the function that calls the private constructor.
|
||||||
|
@ -387,16 +398,72 @@ static const PLDHashTableOps AtomTableOps = {
|
||||||
|
|
||||||
//----------------------------------------------------------------------
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
|
void
|
||||||
|
DynamicAtom::GCAtomTable()
|
||||||
|
{
|
||||||
|
MutexAutoLock lock(*gAtomTableLock);
|
||||||
|
uint32_t removedCount = 0; // Use a non-atomic temporary for cheaper increments.
|
||||||
|
for (auto i = gAtomTable->Iter(); !i.Done(); i.Next()) {
|
||||||
|
auto entry = static_cast<AtomTableEntry*>(i.Get());
|
||||||
|
if (!entry->mAtom->IsStaticAtom()) {
|
||||||
|
auto atom = static_cast<DynamicAtom*>(entry->mAtom);
|
||||||
|
if (atom->mRefCnt == 0) {
|
||||||
|
i.Remove();
|
||||||
|
delete atom;
|
||||||
|
++removedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// During the course of this function, the atom table is locked. This means
|
||||||
|
// that, barring refcounting bugs in consumers, an atom can never go from
|
||||||
|
// refcount == 0 to refcount != 0 during a GC. However, an atom _can_ go from
|
||||||
|
// refcount != 0 to refcount == 0 if a Release() occurs in parallel with GC.
|
||||||
|
// This means that we cannot assert that gUnusedAtomCount == removedCount, and
|
||||||
|
// thus that there are no unused atoms at the end of a GC. We can and do,
|
||||||
|
// however, assert this after the last GC at shutdown.
|
||||||
|
MOZ_ASSERT(removedCount <= gUnusedAtomCount);
|
||||||
|
gUnusedAtomCount -= removedCount;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMPL_QUERY_INTERFACE(DynamicAtom, nsIAtom)
|
||||||
|
|
||||||
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||||
|
DynamicAtom::AddRef(void)
|
||||||
|
{
|
||||||
|
nsrefcnt count = ++mRefCnt;
|
||||||
|
if (count == 1) {
|
||||||
|
MOZ_ASSERT(gUnusedAtomCount > 0);
|
||||||
|
gUnusedAtomCount--;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
// We set a lower GC threshold for atoms in debug builds so that we exercise
|
||||||
|
// the GC machinery more often.
|
||||||
|
static const uint32_t kAtomGCThreshold = 20;
|
||||||
|
#else
|
||||||
|
static const uint32_t kAtomGCThreshold = 10000;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||||
|
DynamicAtom::Release(void)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(mRefCnt > 0);
|
||||||
|
nsrefcnt count = --mRefCnt;
|
||||||
|
if (count == 0) {
|
||||||
|
if (++gUnusedAtomCount >= kAtomGCThreshold) {
|
||||||
|
GCAtomTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
DynamicAtom::~DynamicAtom()
|
DynamicAtom::~DynamicAtom()
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(gAtomTable, "uninitialized atom hashtable");
|
|
||||||
MutexAutoLock lock(*gAtomTableLock);
|
|
||||||
|
|
||||||
// DynamicAtoms must be removed from gAtomTable when their refcount reaches
|
|
||||||
// zero and they are released.
|
|
||||||
AtomTableKey key(mString, mLength, mHash);
|
|
||||||
gAtomTable->Remove(&key);
|
|
||||||
|
|
||||||
nsStringBuffer::FromData(mString)->Release();
|
nsStringBuffer::FromData(mString)->Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,6 +541,13 @@ NS_ShutdownAtomTable()
|
||||||
delete gStaticAtomTable;
|
delete gStaticAtomTable;
|
||||||
gStaticAtomTable = nullptr;
|
gStaticAtomTable = nullptr;
|
||||||
|
|
||||||
|
#ifdef NS_FREE_PERMANENT_DATA
|
||||||
|
// Do a final GC to satisfy leak checking. We skip this step in release
|
||||||
|
// builds.
|
||||||
|
DynamicAtom::GCAtomTable();
|
||||||
|
MOZ_ASSERT(gUnusedAtomCount == 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
// XXXbholley: it would be good to assert gAtomTable->EntryCount() == 0
|
// XXXbholley: it would be good to assert gAtomTable->EntryCount() == 0
|
||||||
// here, but that currently fails. Probably just a few things that need
|
// here, but that currently fails. Probably just a few things that need
|
||||||
// to be fixed up.
|
// to be fixed up.
|
||||||
|
@ -589,7 +663,7 @@ NS_Atomize(const nsACString& aUTF8String)
|
||||||
// Actually, now there is, sort of: ForgetSharedBuffer.
|
// Actually, now there is, sort of: ForgetSharedBuffer.
|
||||||
nsString str;
|
nsString str;
|
||||||
CopyUTF8toUTF16(aUTF8String, str);
|
CopyUTF8toUTF16(aUTF8String, str);
|
||||||
RefPtr<DynamicAtom> atom = new DynamicAtom(str, hash);
|
RefPtr<DynamicAtom> atom = DynamicAtom::Create(str, hash);
|
||||||
|
|
||||||
he->mAtom = atom;
|
he->mAtom = atom;
|
||||||
|
|
||||||
|
@ -617,7 +691,7 @@ NS_Atomize(const nsAString& aUTF16String)
|
||||||
return atom.forget();
|
return atom.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<DynamicAtom> atom = new DynamicAtom(aUTF16String, hash);
|
RefPtr<DynamicAtom> atom = DynamicAtom::Create(aUTF16String, hash);
|
||||||
he->mAtom = atom;
|
he->mAtom = atom;
|
||||||
|
|
||||||
return atom.forget();
|
return atom.forget();
|
||||||
|
@ -626,6 +700,7 @@ NS_Atomize(const nsAString& aUTF16String)
|
||||||
nsrefcnt
|
nsrefcnt
|
||||||
NS_GetNumberOfAtoms(void)
|
NS_GetNumberOfAtoms(void)
|
||||||
{
|
{
|
||||||
|
DynamicAtom::GCAtomTable(); // Trigger a GC so that we return a deterministic result.
|
||||||
MutexAutoLock lock(*gAtomTableLock);
|
MutexAutoLock lock(*gAtomTableLock);
|
||||||
return gAtomTable->EntryCount();
|
return gAtomTable->EntryCount();
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче