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:
parent
0c7fe9a33e
commit
c749f37c83
|
@ -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)
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue