Fix quadratic behavior in the memory manager

The old freeList implementation was causing quadratic
behavior in alloc(), as the free item ended up in the
highest chunk.

The new implementation uses a fixed size array for small objects
(up to 256 bytes), a QMap for large chunks, and a defaultFree
object pointing to the heap that has never been used before.

Gives around 25% performance boost on crypto.js, and bsaically
makes the memory manager invisible in kcachegrind.

Change-Id: I559fb527bcd9e21d4ac265f4d78b8376bfda2522
Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
This commit is contained in:
Lars Knoll 2012-12-13 23:46:51 +01:00 committed by Simon Hausmann
parent 2761c4ceac
commit 21d90ca005
2 changed files with 88 additions and 51 deletions

137
qv4mm.cpp
View File

@ -36,6 +36,7 @@
#include <QTime>
#include <QVector>
#include <QLinkedList>
#include <QMap>
#include <iostream>
#include <cstdlib>
@ -53,10 +54,9 @@ struct MemoryManager::Data
ExecutionEngine *engine;
StringPool *stringPool;
// FIXME: this freeList will get out of hand if there is one allocation+deallocation of, say, 16M.
// TODO: turn the freeList into a fixed length array which can hold the most common sizes (e.g. up to 64K), then use a tree for anything afterwards.
// TODO: this requires that the interaction with the freeList is factored out first into separate methods.
QVector<MMObject *> freeList;
MMObject *fallbackObject;
MMObject *smallItems[16]; // 16 - 256 bytes
QMap<size_t, MMObject *> largeItems;
QLinkedList<QPair<char *, std::size_t> > heapChunks;
// statistics:
@ -69,8 +69,9 @@ struct MemoryManager::Data
, gcBlocked(false)
, engine(0)
, stringPool(0)
, freeList(0)
, fallbackObject(0)
{
memset(smallItems, 0, sizeof(smallItems));
scribble = qgetenv("MM_NO_SCRIBBLE").isEmpty();
aggressiveGC = !qgetenv("MM_AGGRESSIVE_GC").isEmpty();
}
@ -96,60 +97,76 @@ MemoryManager::MMObject *MemoryManager::alloc(std::size_t size)
#endif // DETAILED_MM_STATS
size += align(sizeof(MMInfo));
assert(size >= 16);
assert(size % 16 == 0);
for (std::size_t s = size / 16, es = m_d->freeList.size(); s < es; ++s) {
if (MMObject *m = m_d->freeList.at(s)) {
m_d->freeList[s] = m->info.next;
if (s != size / 16) {
MMObject *tail = reinterpret_cast<MMObject *>(reinterpret_cast<char *>(m) + size);
assert(m->info.size == s * 16);
tail->info.inUse = 0;
tail->info.markBit = 0;
tail->info.size = m->info.size - size;
MMObject *&f = m_d->freeList[tail->info.size / 16];
tail->info.next = f;
f = tail;
m->info.size = size;
}
size_t pos = size >> 4;
// fits into a small bucket
if (pos < sizeof(m_d->smallItems)/sizeof(MMObject *)) {
MMObject *m = m_d->smallItems[pos];
if (m) {
m_d->smallItems[pos] = m->info.next;
m->info.inUse = 1;
m->info.markBit = 0;
scribble(m, 0xaa);
// qDebug("alloc(%lu) -> %p", size, m);
return m;
}
}
if (!m_d->aggressiveGC)
if (runGC() >= size)
return alloc(size - sizeof(MMInfo));
std::size_t allocSize = std::max(size, CHUNK_SIZE);
char *ptr = 0;
posix_memalign(reinterpret_cast<void**>(&ptr), 16, allocSize);
m_d->heapChunks.append(qMakePair(ptr, allocSize));
// qDebug("Allocated new chunk of %lu bytes @ %p", allocSize, ptr);
if (allocSize > size) {
MMObject *m = reinterpret_cast<MMObject *>(ptr + size);
m->info.size = allocSize - size;
std::size_t off = m->info.size / 16;
if (((std::size_t) m_d->freeList.size()) <= off)
m_d->freeList.resize(off + 1);
MMObject *&f = m_d->freeList[off];
m->info.next = f;
f = m;
// ### use new heap space if available
if (m_d->fallbackObject && m_d->fallbackObject->info.size >= size) {
MMObject *m = m_d->fallbackObject;
m_d->fallbackObject = splitItem(m, size);
m->info.inUse = 1;
m->info.markBit = 0;
return m;
}
MMObject *m = reinterpret_cast<MMObject *>(ptr);
// use or split up a large bucket
QMap<size_t, MMObject *>::iterator it = m_d->largeItems.lowerBound(pos);
if (it == m_d->largeItems.end()) {
// try to free up space, otherwise allocate
if (!m_d->aggressiveGC || runGC() < size) {
std::size_t allocSize = std::max(size, CHUNK_SIZE);
char *ptr = 0;
posix_memalign(reinterpret_cast<void**>(&ptr), 16, allocSize);
m_d->heapChunks.append(qMakePair(ptr, allocSize));
m_d->fallbackObject = reinterpret_cast<MMObject *>(ptr);
m_d->fallbackObject->info.inUse = 0;
m_d->fallbackObject->info.next = 0;
m_d->fallbackObject->info.markBit = 0;
m_d->fallbackObject->info.size = allocSize;
}
return alloc(size - sizeof(MMInfo));
}
MMObject *m = it.value();
assert(m);
if (it.key() == pos) {
// a match, return it
if (!m->info.next)
m_d->largeItems.erase(it);
else
*it = m->info.next;
m->info.inUse = 1;
m->info.markBit = 0;
return m;
}
// split up
if (!m->info.next)
m_d->largeItems.erase(it);
else
*it = m->info.next;
MMObject *tail = splitItem(m, size);
MMObject *&f = m_d->largeItems[tail->info.size];
tail->info.next = f;
f = tail;
m->info.inUse = 1;
m->info.markBit = 0;
m->info.size = size;
scribble(m, 0xaa);
// qDebug("alloc(%lu) -> %p", size, ptr);
return m;
}
@ -163,16 +180,33 @@ void MemoryManager::dealloc(MMObject *ptr)
// qDebug("dealloc %p (%lu)", ptr, ptr->info.size);
std::size_t off = ptr->info.size / 16;
if (((std::size_t) m_d->freeList.size()) <= off)
m_d->freeList.resize(off + 1);
MMObject *&f = m_d->freeList[off];
ptr->info.next = f;
std::size_t pos = ptr->info.size >> 4;
MMObject **f;
// fits into a small bucket
if (pos < sizeof(m_d->smallItems)/sizeof(MMObject *)) {
f = &m_d->smallItems[pos];
} else {
f = &m_d->largeItems[pos];
}
ptr->info.next = *f;
ptr->info.inUse = 0;
ptr->info.markBit = 0;
ptr->info.needsManagedDestructorCall = 0;
f = ptr;
scribble(ptr, 0x55);
*f = ptr;
}
MemoryManager::MMObject *MemoryManager::splitItem(MemoryManager::MMObject *m, int newSize)
{
if (newSize - m->info.size <= sizeof(MMObject))
return 0;
MMObject *tail = reinterpret_cast<MMObject *>(reinterpret_cast<char *>(m) + newSize);
tail->info.inUse = 0;
tail->info.markBit = 0;
tail->info.size = m->info.size - newSize;
m->info.size = newSize;
return tail;
}
void MemoryManager::scribble(MemoryManager::MMObject *obj, int c) const
@ -344,6 +378,7 @@ void MemoryManager::willAllocate(std::size_t size)
counters.resize(alignedSize + 1);
counters[alignedSize]++;
}
#endif // DETAILED_MM_STATS
void MemoryManager::collectRoots(QVector<VM::Object *> &roots) const

View File

@ -153,6 +153,8 @@ protected:
void willAllocate(std::size_t size);
#endif // DETAILED_MM_STATS
MMObject *splitItem(MMObject *m, int newSize);
private:
void collectRoots(QVector<VM::Object *> &roots) const;
static std::size_t mark(const QVector<Object *> &objects);