V4: track C++ heap usage for Strings in the MemoryManager

... and do a GC run when it exceeds a threshold. The issue with Strings
is that they hold on to QString instances that store the real content.
However, the GC only sees the light-weight JS handle, and doesn't take
the size of the backing content into account. So it could happen that
big QStrings accumulate in the heap as long as the GC didn't reach its
threshold.

The newly introduced unmanaged heap threshold is upped by a factor of
two when exceeded, and lowered by a factor of 2 when the used heap space
falls below a quarter of the threshold. Also grow the threshold if there
is enough space after running the GC, but another GC run would be
triggered for the next allocation.

There is a special case for Heap::String::append, because this method
will copy the data from the left and right substrings into a new
QString. To track this, append notifies the memory manager directly of
the new length. The pointer to the memory manager is stored in
Heap::String, growing it from 40 bytes to 48 bytes (which makes it still
fit in the same bucket, so no extra memory is allocated).

Task-number: QTBUG-42002
Change-Id: I71313915e593a9908a2b227b0bc4d768e375ee17
Reviewed-by: Simon Hausmann <simon.hausmann@theqtcompany.com>
This commit is contained in:
Erik Verbruggen 2015-07-03 13:20:18 +02:00 committed by Erik Verbruggen
parent 0c7fe9a33e
commit c749f37c83
7 changed files with 75 additions and 19 deletions

View File

@ -538,7 +538,7 @@ Heap::Object *ExecutionEngine::newObject(InternalClass *internalClass, QV4::Obje
Heap::String *ExecutionEngine::newString(const QString &s) Heap::String *ExecutionEngine::newString(const QString &s)
{ {
Scope scope(this); Scope scope(this);
return ScopedString(scope, memoryManager->alloc<String>(s))->d(); return ScopedString(scope, memoryManager->allocWithStringData<String>(s.length() * sizeof(QChar), s))->d();
} }
Heap::String *ExecutionEngine::newIdentifier(const QString &text) Heap::String *ExecutionEngine::newIdentifier(const QString &text)

View File

@ -66,7 +66,8 @@ Function::Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit,
break; break;
} }
// duplicate arguments, need some trick to store them // duplicate arguments, need some trick to store them
arg = engine->memoryManager->alloc<String>(arg->d(), engine->newString(QString(0xfffe))); MemoryManager *mm = engine->memoryManager;
arg = mm->alloc<String>(mm, arg->d(), engine->newString(QString(0xfffe)));
} }
} }

View File

@ -95,6 +95,8 @@ struct MemoryManager::Data
uint maxShift; uint maxShift;
std::size_t maxChunkSize; std::size_t maxChunkSize;
QVector<PageAllocation> heapChunks; QVector<PageAllocation> heapChunks;
std::size_t unmanagedHeapSize; // the amount of bytes of heap that is not managed by the memory manager, but which is held onto by managed items.
std::size_t unmanagedHeapSizeGCLimit;
struct LargeItem { struct LargeItem {
LargeItem *next; LargeItem *next;
@ -123,6 +125,8 @@ struct MemoryManager::Data
, totalAlloc(0) , totalAlloc(0)
, maxShift(6) , maxShift(6)
, maxChunkSize(32*1024) , maxChunkSize(32*1024)
, unmanagedHeapSize(0)
, unmanagedHeapSizeGCLimit(64 * 1024)
, largeItems(0) , largeItems(0)
, totalLargeItemsAllocated(0) , totalLargeItemsAllocated(0)
, deletable(0) , deletable(0)
@ -157,8 +161,10 @@ struct MemoryManager::Data
namespace { namespace {
bool sweepChunk(MemoryManager::Data::ChunkHeader *header, uint *itemsInUse, ExecutionEngine *engine) bool sweepChunk(MemoryManager::Data::ChunkHeader *header, uint *itemsInUse, ExecutionEngine *engine, std::size_t *unmanagedHeapSize)
{ {
Q_ASSERT(unmanagedHeapSize);
bool isEmpty = true; bool isEmpty = true;
Heap::Base *tail = &header->freeItems; Heap::Base *tail = &header->freeItems;
// qDebug("chunkStart @ %p, size=%x, pos=%x", header->itemStart, header->itemSize, header->itemSize>>4); // qDebug("chunkStart @ %p, size=%x, pos=%x", header->itemStart, header->itemSize, header->itemSize>>4);
@ -167,8 +173,8 @@ bool sweepChunk(MemoryManager::Data::ChunkHeader *header, uint *itemsInUse, Exec
#endif #endif
for (char *item = header->itemStart; item <= header->itemEnd; item += header->itemSize) { for (char *item = header->itemStart; item <= header->itemEnd; item += header->itemSize) {
Heap::Base *m = reinterpret_cast<Heap::Base *>(item); Heap::Base *m = reinterpret_cast<Heap::Base *>(item);
// qDebug("chunk @ %p, size = %lu, in use: %s, mark bit: %s", // qDebug("chunk @ %p, in use: %s, mark bit: %s",
// item, m->size, (m->inUse ? "yes" : "no"), (m->markBit ? "true" : "false")); // item, (m->inUse() ? "yes" : "no"), (m->isMarked() ? "true" : "false"));
Q_ASSERT((qintptr) item % 16 == 0); Q_ASSERT((qintptr) item % 16 == 0);
@ -183,6 +189,13 @@ bool sweepChunk(MemoryManager::Data::ChunkHeader *header, uint *itemsInUse, Exec
#ifdef V4_USE_VALGRIND #ifdef V4_USE_VALGRIND
VALGRIND_ENABLE_ERROR_REPORTING; VALGRIND_ENABLE_ERROR_REPORTING;
#endif #endif
if (std::size_t(header->itemSize) == MemoryManager::align(sizeof(Heap::String)) && m->gcGetVtable()->isString) {
std::size_t heapBytes = static_cast<Heap::String *>(m)->retainedTextSize();
Q_ASSERT(*unmanagedHeapSize >= heapBytes);
// qDebug() << "-- it's a string holding on to" << heapBytes << "bytes";
*unmanagedHeapSize -= heapBytes;
}
if (m->gcGetVtable()->destroy) if (m->gcGetVtable()->destroy)
m->gcGetVtable()->destroy(m); m->gcGetVtable()->destroy(m);
@ -219,7 +232,7 @@ MemoryManager::MemoryManager(ExecutionEngine *engine)
m_d->engine = engine; m_d->engine = engine;
} }
Heap::Base *MemoryManager::allocData(std::size_t size) Heap::Base *MemoryManager::allocData(std::size_t size, std::size_t unmanagedSize)
{ {
if (m_d->aggressiveGC) if (m_d->aggressiveGC)
runGC(); runGC();
@ -230,11 +243,27 @@ Heap::Base *MemoryManager::allocData(std::size_t size)
Q_ASSERT(size >= 16); Q_ASSERT(size >= 16);
Q_ASSERT(size % 16 == 0); Q_ASSERT(size % 16 == 0);
// qDebug() << "unmanagedHeapSize:" << m_d->unmanagedHeapSize << "limit:" << m_d->unmanagedHeapSizeGCLimit << "unmanagedSize:" << unmanagedSize;
m_d->unmanagedHeapSize += unmanagedSize;
bool didGCRun = false;
if (m_d->unmanagedHeapSize > m_d->unmanagedHeapSizeGCLimit) {
runGC();
if (m_d->unmanagedHeapSizeGCLimit <= m_d->unmanagedHeapSize)
m_d->unmanagedHeapSizeGCLimit = std::max(m_d->unmanagedHeapSizeGCLimit, m_d->unmanagedHeapSize) * 2;
else if (m_d->unmanagedHeapSize * 4 <= m_d->unmanagedHeapSizeGCLimit)
m_d->unmanagedHeapSizeGCLimit /= 2;
else if (m_d->unmanagedHeapSizeGCLimit - m_d->unmanagedHeapSize < 5 * unmanagedSize)
// try preventing running the GC all the time when we're just below the threshold limit and manage to collect just enough to do this one allocation
m_d->unmanagedHeapSizeGCLimit += std::max(std::size_t(8 * 1024), 5 * unmanagedSize);
didGCRun = true;
}
size_t pos = size >> 4; size_t pos = size >> 4;
// doesn't fit into a small bucket // doesn't fit into a small bucket
if (size >= MemoryManager::Data::MaxItemSize) { if (size >= MemoryManager::Data::MaxItemSize) {
if (m_d->totalLargeItemsAllocated > 8 * 1024 * 1024) if (!didGCRun && m_d->totalLargeItemsAllocated > 8 * 1024 * 1024)
runGC(); runGC();
// we use malloc for this // we use malloc for this
@ -257,7 +286,7 @@ Heap::Base *MemoryManager::allocData(std::size_t size)
} }
// try to free up space, otherwise allocate // try to free up space, otherwise allocate
if (m_d->allocCount[pos] > (m_d->availableItems[pos] >> 1) && m_d->totalAlloc > (m_d->totalItems >> 1) && !m_d->aggressiveGC) { if (!didGCRun && m_d->allocCount[pos] > (m_d->availableItems[pos] >> 1) && m_d->totalAlloc > (m_d->totalItems >> 1) && !m_d->aggressiveGC) {
runGC(); runGC();
header = m_d->nonFullChunks[pos]; header = m_d->nonFullChunks[pos];
if (header) { if (header) {
@ -404,7 +433,7 @@ void MemoryManager::sweep(bool lastSweep)
for (int i = 0; i < m_d->heapChunks.size(); ++i) { for (int i = 0; i < m_d->heapChunks.size(); ++i) {
Data::ChunkHeader *header = reinterpret_cast<Data::ChunkHeader *>(m_d->heapChunks[i].base()); Data::ChunkHeader *header = reinterpret_cast<Data::ChunkHeader *>(m_d->heapChunks[i].base());
chunkIsEmpty[i] = sweepChunk(header, &itemsInUse[header->itemSize >> 4], m_d->engine); chunkIsEmpty[i] = sweepChunk(header, &itemsInUse[header->itemSize >> 4], m_d->engine, &m_d->unmanagedHeapSize);
} }
QVector<PageAllocation>::iterator chunkIter = m_d->heapChunks.begin(); QVector<PageAllocation>::iterator chunkIter = m_d->heapChunks.begin();
@ -553,6 +582,11 @@ size_t MemoryManager::getLargeItemsMem() const
return total; return total;
} }
void MemoryManager::growUnmanagedHeapSizeUsage(size_t delta)
{
m_d->unmanagedHeapSize += delta;
}
MemoryManager::~MemoryManager() MemoryManager::~MemoryManager()
{ {
delete m_persistentValues; delete m_persistentValues;

View File

@ -83,10 +83,10 @@ public:
{ return (size + 15) & ~0xf; } { return (size + 15) & ~0xf; }
template<typename ManagedType> template<typename ManagedType>
inline typename ManagedType::Data *allocManaged(std::size_t size) inline typename ManagedType::Data *allocManaged(std::size_t size, std::size_t unmanagedSize = 0)
{ {
size = align(size); size = align(size);
Heap::Base *o = allocData(size); Heap::Base *o = allocData(size, unmanagedSize);
o->vtable = ManagedType::staticVTable(); o->vtable = ManagedType::staticVTable();
return static_cast<typename ManagedType::Data *>(o); return static_cast<typename ManagedType::Data *>(o);
} }
@ -109,6 +109,15 @@ public:
return t->d(); return t->d();
} }
template <typename ManagedType, typename Arg1>
typename ManagedType::Data *allocWithStringData(std::size_t unmanagedSize, Arg1 arg1)
{
Scope scope(engine());
Scoped<ManagedType> t(scope, allocManaged<ManagedType>(sizeof(typename ManagedType::Data), unmanagedSize));
(void)new (t->d()) typename ManagedType::Data(this, arg1);
return t->d();
}
template <typename ManagedType, typename Arg1, typename Arg2> template <typename ManagedType, typename Arg1, typename Arg2>
typename ManagedType::Data *alloc(Arg1 arg1, Arg2 arg2) typename ManagedType::Data *alloc(Arg1 arg1, Arg2 arg2)
{ {
@ -159,10 +168,12 @@ public:
size_t getAllocatedMem() const; size_t getAllocatedMem() const;
size_t getLargeItemsMem() const; size_t getLargeItemsMem() const;
void growUnmanagedHeapSizeUsage(size_t delta); // called when a JS object grows itself. Specifically: Heap::String::append
protected: protected:
/// expects size to be aligned /// expects size to be aligned
// TODO: try to inline // TODO: try to inline
Heap::Base *allocData(std::size_t size); Heap::Base *allocData(std::size_t size, std::size_t unmanagedSize);
#ifdef DETAILED_MM_STATS #ifdef DETAILED_MM_STATS
void willAllocate(std::size_t size); void willAllocate(std::size_t size);

View File

@ -521,7 +521,8 @@ QV4::ReturnedValue RuntimeHelpers::addHelper(ExecutionEngine *engine, const Valu
return pright->asReturnedValue(); return pright->asReturnedValue();
if (!pright->stringValue()->d()->length()) if (!pright->stringValue()->d()->length())
return pleft->asReturnedValue(); return pleft->asReturnedValue();
return (engine->memoryManager->alloc<String>(pleft->stringValue()->d(), pright->stringValue()->d()))->asReturnedValue(); MemoryManager *mm = engine->memoryManager;
return (mm->alloc<String>(mm, pleft->stringValue()->d(), pright->stringValue()->d()))->asReturnedValue();
} }
double x = RuntimeHelpers::toNumber(pleft); double x = RuntimeHelpers::toNumber(pleft);
double y = RuntimeHelpers::toNumber(pright); double y = RuntimeHelpers::toNumber(pright);
@ -537,7 +538,8 @@ QV4::ReturnedValue Runtime::addString(ExecutionEngine *engine, const Value &left
return right.asReturnedValue(); return right.asReturnedValue();
if (!right.stringValue()->d()->length()) if (!right.stringValue()->d()->length())
return left.asReturnedValue(); return left.asReturnedValue();
return (engine->memoryManager->alloc<String>(left.stringValue()->d(), right.stringValue()->d()))->asReturnedValue(); MemoryManager *mm = engine->memoryManager;
return (mm->alloc<String>(mm, left.stringValue()->d(), right.stringValue()->d()))->asReturnedValue();
} }
Scope scope(engine); Scope scope(engine);
@ -554,7 +556,8 @@ QV4::ReturnedValue Runtime::addString(ExecutionEngine *engine, const Value &left
return pright->asReturnedValue(); return pright->asReturnedValue();
if (!pright->stringValue()->d()->length()) if (!pright->stringValue()->d()->length())
return pleft->asReturnedValue(); return pleft->asReturnedValue();
return (engine->memoryManager->alloc<String>(pleft->stringValue()->d(), pright->stringValue()->d()))->asReturnedValue(); MemoryManager *mm = engine->memoryManager;
return (mm->alloc<String>(mm, pleft->stringValue()->d(), pright->stringValue()->d()))->asReturnedValue();
} }
void Runtime::setProperty(ExecutionEngine *engine, const Value &object, int nameIndex, const Value &value) void Runtime::setProperty(ExecutionEngine *engine, const Value &object, int nameIndex, const Value &value)

View File

@ -117,7 +117,8 @@ bool String::isEqualTo(Managed *t, Managed *o)
} }
Heap::String::String(const QString &t) Heap::String::String(MemoryManager *mm, const QString &t)
: mm(mm)
{ {
subtype = String::StringType_Unknown; subtype = String::StringType_Unknown;
@ -129,7 +130,8 @@ Heap::String::String(const QString &t)
len = text->size; len = text->size;
} }
Heap::String::String(String *l, String *r) Heap::String::String(MemoryManager *mm, String *l, String *r)
: mm(mm)
{ {
subtype = String::StringType_Unknown; subtype = String::StringType_Unknown;
@ -187,6 +189,7 @@ void Heap::String::simplifyString() const
text->ref.ref(); text->ref.ref();
identifier = 0; identifier = 0;
largestSubLength = 0; largestSubLength = 0;
mm->growUnmanagedHeapSizeUsage(size_t(text->size) * sizeof(QChar));
} }
void Heap::String::createHashValue() const void Heap::String::createHashValue() const

View File

@ -53,8 +53,8 @@ struct Q_QML_PRIVATE_EXPORT String : Base {
StringType_ArrayIndex StringType_ArrayIndex
}; };
String(const QString &text); String(MemoryManager *mm, const QString &text);
String(String *l, String *n); String(MemoryManager *mm, String *l, String *n);
~String() { ~String() {
if (!largestSubLength && !text->ref.deref()) if (!largestSubLength && !text->ref.deref())
QStringData::deallocate(text); QStringData::deallocate(text);
@ -66,6 +66,9 @@ struct Q_QML_PRIVATE_EXPORT String : Base {
len == (uint)text->size); len == (uint)text->size);
return len; return len;
} }
std::size_t retainedTextSize() const {
return largestSubLength ? 0 : (std::size_t(text->size) * sizeof(QChar));
}
void createHashValue() const; void createHashValue() const;
inline unsigned hashValue() const { inline unsigned hashValue() const {
if (subtype == StringType_Unknown) if (subtype == StringType_Unknown)
@ -107,6 +110,7 @@ struct Q_QML_PRIVATE_EXPORT String : Base {
mutable uint stringHash; mutable uint stringHash;
mutable uint largestSubLength; mutable uint largestSubLength;
uint len; uint len;
MemoryManager *mm;
private: private:
static void append(const String *data, QChar *ch); static void append(const String *data, QChar *ch);
}; };