diff options
author | Erik Verbruggen <erik.verbruggen@digia.com> | 2015-07-03 13:20:18 +0200 |
---|---|---|
committer | Erik Verbruggen <erik.verbruggen@theqtcompany.com> | 2015-07-10 13:52:18 +0000 |
commit | c749f37c83cbb458e25a7d5200facf8634ac959e (patch) | |
tree | a1dc5909acccfb67f5534ff459ea3dae924e80f5 /src | |
parent | 0c7fe9a33e696b8a319f96daaaf730ed03e9b233 (diff) |
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>
Diffstat (limited to 'src')
-rw-r--r-- | src/qml/jsruntime/qv4engine.cpp | 2 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4function.cpp | 3 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4mm.cpp | 48 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4mm_p.h | 17 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4runtime.cpp | 9 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4string.cpp | 7 | ||||
-rw-r--r-- | src/qml/jsruntime/qv4string_p.h | 8 |
7 files changed, 75 insertions, 19 deletions
diff --git a/src/qml/jsruntime/qv4engine.cpp b/src/qml/jsruntime/qv4engine.cpp index 2dbdcfc526..0f2b44fac6 100644 --- a/src/qml/jsruntime/qv4engine.cpp +++ b/src/qml/jsruntime/qv4engine.cpp @@ -538,7 +538,7 @@ Heap::Object *ExecutionEngine::newObject(InternalClass *internalClass, QV4::Obje Heap::String *ExecutionEngine::newString(const QString &s) { 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) diff --git a/src/qml/jsruntime/qv4function.cpp b/src/qml/jsruntime/qv4function.cpp index efe6c7c226..be63b31e1e 100644 --- a/src/qml/jsruntime/qv4function.cpp +++ b/src/qml/jsruntime/qv4function.cpp @@ -66,7 +66,8 @@ Function::Function(ExecutionEngine *engine, CompiledData::CompilationUnit *unit, break; } // 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))); } } diff --git a/src/qml/jsruntime/qv4mm.cpp b/src/qml/jsruntime/qv4mm.cpp index d5576b400a..64491e1376 100644 --- a/src/qml/jsruntime/qv4mm.cpp +++ b/src/qml/jsruntime/qv4mm.cpp @@ -95,6 +95,8 @@ struct MemoryManager::Data uint maxShift; std::size_t maxChunkSize; 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 { LargeItem *next; @@ -123,6 +125,8 @@ struct MemoryManager::Data , totalAlloc(0) , maxShift(6) , maxChunkSize(32*1024) + , unmanagedHeapSize(0) + , unmanagedHeapSizeGCLimit(64 * 1024) , largeItems(0) , totalLargeItemsAllocated(0) , deletable(0) @@ -157,8 +161,10 @@ struct MemoryManager::Data 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; Heap::Base *tail = &header->freeItems; // 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 for (char *item = header->itemStart; item <= header->itemEnd; item += header->itemSize) { Heap::Base *m = reinterpret_cast<Heap::Base *>(item); -// qDebug("chunk @ %p, size = %lu, in use: %s, mark bit: %s", -// item, m->size, (m->inUse ? "yes" : "no"), (m->markBit ? "true" : "false")); +// qDebug("chunk @ %p, in use: %s, mark bit: %s", +// item, (m->inUse() ? "yes" : "no"), (m->isMarked() ? "true" : "false")); Q_ASSERT((qintptr) item % 16 == 0); @@ -183,6 +189,13 @@ bool sweepChunk(MemoryManager::Data::ChunkHeader *header, uint *itemsInUse, Exec #ifdef V4_USE_VALGRIND VALGRIND_ENABLE_ERROR_REPORTING; #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) m->gcGetVtable()->destroy(m); @@ -219,7 +232,7 @@ MemoryManager::MemoryManager(ExecutionEngine *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) runGC(); @@ -230,11 +243,27 @@ Heap::Base *MemoryManager::allocData(std::size_t size) Q_ASSERT(size >= 16); 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; // doesn't fit into a small bucket if (size >= MemoryManager::Data::MaxItemSize) { - if (m_d->totalLargeItemsAllocated > 8 * 1024 * 1024) + if (!didGCRun && m_d->totalLargeItemsAllocated > 8 * 1024 * 1024) runGC(); // we use malloc for this @@ -257,7 +286,7 @@ Heap::Base *MemoryManager::allocData(std::size_t size) } // 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(); header = m_d->nonFullChunks[pos]; if (header) { @@ -404,7 +433,7 @@ void MemoryManager::sweep(bool lastSweep) for (int i = 0; i < m_d->heapChunks.size(); ++i) { 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(); @@ -553,6 +582,11 @@ size_t MemoryManager::getLargeItemsMem() const return total; } +void MemoryManager::growUnmanagedHeapSizeUsage(size_t delta) +{ + m_d->unmanagedHeapSize += delta; +} + MemoryManager::~MemoryManager() { delete m_persistentValues; diff --git a/src/qml/jsruntime/qv4mm_p.h b/src/qml/jsruntime/qv4mm_p.h index 00b41b796a..7f6d2edba3 100644 --- a/src/qml/jsruntime/qv4mm_p.h +++ b/src/qml/jsruntime/qv4mm_p.h @@ -83,10 +83,10 @@ public: { return (size + 15) & ~0xf; } 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); - Heap::Base *o = allocData(size); + Heap::Base *o = allocData(size, unmanagedSize); o->vtable = ManagedType::staticVTable(); return static_cast<typename ManagedType::Data *>(o); } @@ -109,6 +109,15 @@ public: 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> typename ManagedType::Data *alloc(Arg1 arg1, Arg2 arg2) { @@ -159,10 +168,12 @@ public: size_t getAllocatedMem() const; size_t getLargeItemsMem() const; + void growUnmanagedHeapSizeUsage(size_t delta); // called when a JS object grows itself. Specifically: Heap::String::append + protected: /// expects size to be aligned // 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 void willAllocate(std::size_t size); diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp index c31de6a9f0..b66e917b87 100644 --- a/src/qml/jsruntime/qv4runtime.cpp +++ b/src/qml/jsruntime/qv4runtime.cpp @@ -521,7 +521,8 @@ QV4::ReturnedValue RuntimeHelpers::addHelper(ExecutionEngine *engine, const Valu return pright->asReturnedValue(); if (!pright->stringValue()->d()->length()) 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 y = RuntimeHelpers::toNumber(pright); @@ -537,7 +538,8 @@ QV4::ReturnedValue Runtime::addString(ExecutionEngine *engine, const Value &left return right.asReturnedValue(); if (!right.stringValue()->d()->length()) 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); @@ -554,7 +556,8 @@ QV4::ReturnedValue Runtime::addString(ExecutionEngine *engine, const Value &left return pright->asReturnedValue(); if (!pright->stringValue()->d()->length()) 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) diff --git a/src/qml/jsruntime/qv4string.cpp b/src/qml/jsruntime/qv4string.cpp index 20dd84420c..6d55eb2c18 100644 --- a/src/qml/jsruntime/qv4string.cpp +++ b/src/qml/jsruntime/qv4string.cpp @@ -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; @@ -129,7 +130,8 @@ Heap::String::String(const QString &t) 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; @@ -187,6 +189,7 @@ void Heap::String::simplifyString() const text->ref.ref(); identifier = 0; largestSubLength = 0; + mm->growUnmanagedHeapSizeUsage(size_t(text->size) * sizeof(QChar)); } void Heap::String::createHashValue() const diff --git a/src/qml/jsruntime/qv4string_p.h b/src/qml/jsruntime/qv4string_p.h index 5a0c83b4b9..1cf8f51a29 100644 --- a/src/qml/jsruntime/qv4string_p.h +++ b/src/qml/jsruntime/qv4string_p.h @@ -53,8 +53,8 @@ struct Q_QML_PRIVATE_EXPORT String : Base { StringType_ArrayIndex }; - String(const QString &text); - String(String *l, String *n); + String(MemoryManager *mm, const QString &text); + String(MemoryManager *mm, String *l, String *n); ~String() { if (!largestSubLength && !text->ref.deref()) QStringData::deallocate(text); @@ -66,6 +66,9 @@ struct Q_QML_PRIVATE_EXPORT String : Base { len == (uint)text->size); return len; } + std::size_t retainedTextSize() const { + return largestSubLength ? 0 : (std::size_t(text->size) * sizeof(QChar)); + } void createHashValue() const; inline unsigned hashValue() const { if (subtype == StringType_Unknown) @@ -107,6 +110,7 @@ struct Q_QML_PRIVATE_EXPORT String : Base { mutable uint stringHash; mutable uint largestSubLength; uint len; + MemoryManager *mm; private: static void append(const String *data, QChar *ch); }; |