JSC should dump object size inference statistics
https://bugs.webkit.org/show_bug.cgi?id=97618
Reviewed by Filip Pizlo.
Added an option to dump object size inference statistics.
To see statistics on live objects:
jsc --showHeapStatistics=1
To see cumulative statistics on all objects ever allocated:
jsc --showHeapStatistics=1 --objectsAreImmortal=1
(This is useful for showing GC churn caused by over-allocation.)
To support this second mode, I refactored Zombies to separate out their
immortality feature so I could reuse it.
* heap/Heap.cpp:
(JSC::MarkObject): Helper for making things immortal. We have to checked
for being zapped because blocks start out in this state.
(JSC::StorageStatistics): Gather statistics by walking the heap. Ignore
arrays and hash tables for now because they're not our focus. (We'll
remove these exceptions in future.)
(JSC::Heap::collect): Moved zombify to the end so it wouldn't interfere
with statistics gathering.
(JSC::Heap::showStatistics):
(JSC::Heap::markAllObjects): Factored out helper, so statistics could
take advantage of immortal objects.
(Zombify): Don't mark immortal objects -- that's another class's job now.
(JSC::Zombify::operator()):
(JSC::Heap::zombifyDeadObjects): Take advantage of forEachDeadCell instead
of rolling our own.
* heap/Heap.h:
(Heap):
* heap/MarkedSpace.h:
(MarkedSpace):
(JSC::MarkedSpace::forEachDeadCell): Added, so clients don't have to do
the iteration logic themselves.
* runtime/Options.cpp:
(JSC::Options::initialize):
* runtime/Options.h: New options, listed above. Make sure to initialize
based on environment variable first, so we can override with specific settings.
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@129586 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/heap/Heap.cpp b/Source/JavaScriptCore/heap/Heap.cpp
index c7fb8c0..ca936eb 100644
--- a/Source/JavaScriptCore/heap/Heap.cpp
+++ b/Source/JavaScriptCore/heap/Heap.cpp
@@ -176,6 +176,15 @@
return true;
}
+struct MarkObject : public MarkedBlock::VoidFunctor {
+ void operator()(JSCell* cell)
+ {
+ if (cell->isZapped())
+ return;
+ Heap::heap(cell)->setMarked(cell);
+ }
+};
+
struct Count : public MarkedBlock::CountFunctor {
void operator()(JSCell*) { count(1); }
};
@@ -226,6 +235,74 @@
return m_typeCountSet.release();
}
+class StorageStatistics : public MarkedBlock::VoidFunctor {
+public:
+ StorageStatistics();
+
+ void operator()(JSCell*);
+
+ size_t objectWithOutOfLineStorageCount();
+ size_t objectCount();
+
+ size_t storageSize();
+ size_t storageCapacity();
+
+private:
+ size_t m_objectWithOutOfLineStorageCount;
+ size_t m_objectCount;
+ size_t m_storageSize;
+ size_t m_storageCapacity;
+};
+
+inline StorageStatistics::StorageStatistics()
+ : m_objectWithOutOfLineStorageCount(0)
+ , m_objectCount(0)
+ , m_storageSize(0)
+ , m_storageCapacity(0)
+{
+}
+
+inline void StorageStatistics::operator()(JSCell* cell)
+{
+ if (!cell->isObject())
+ return;
+
+ JSObject* object = jsCast<JSObject*>(cell);
+ if (hasIndexedProperties(object->structure()->indexingType()))
+ return;
+
+ if (object->structure()->isUncacheableDictionary())
+ return;
+
+ ++m_objectCount;
+ if (!object->hasInlineStorage())
+ ++m_objectWithOutOfLineStorageCount;
+ m_storageSize += object->structure()->totalStorageSize() * sizeof(WriteBarrierBase<Unknown>);
+ m_storageCapacity += object->structure()->totalStorageCapacity() * sizeof(WriteBarrierBase<Unknown>);
+}
+
+inline size_t StorageStatistics::objectWithOutOfLineStorageCount()
+{
+ return m_objectWithOutOfLineStorageCount;
+}
+
+inline size_t StorageStatistics::objectCount()
+{
+ return m_objectCount;
+}
+
+
+inline size_t StorageStatistics::storageSize()
+{
+ return m_storageSize;
+}
+
+
+inline size_t StorageStatistics::storageCapacity()
+{
+ return m_storageCapacity;
+}
+
} // anonymous namespace
Heap::Heap(JSGlobalData* globalData, HeapType heapType)
@@ -753,9 +830,6 @@
m_objectSpace.resetAllocators();
}
- if (Options::useZombieMode())
- zombifyDeadObjects();
-
size_t currentHeapSize = size();
if (fullGC) {
m_sizeAfterLastCollect = currentHeapSize;
@@ -769,10 +843,48 @@
m_bytesAllocated = 0;
double lastGCEndTime = WTF::currentTime();
m_lastGCLength = lastGCEndTime - lastGCStartTime;
+
if (m_operationInProgress != Collection)
CRASH();
m_operationInProgress = NoOperation;
JAVASCRIPTCORE_GC_END();
+
+ if (Options::useZombieMode())
+ zombifyDeadObjects();
+
+ if (Options::objectsAreImmortal())
+ markDeadObjects();
+
+ if (Options::showHeapStatistics())
+ showStatistics();
+}
+
+void Heap::showStatistics()
+{
+ dataLog("\n=== Heap Statistics: ===\n");
+ dataLog("size: %ldkB\n", static_cast<long>(m_sizeAfterLastCollect / KB));
+ dataLog("capacity: %ldkB\n", static_cast<long>(capacity() / KB));
+ dataLog("pause time: %lfms\n\n", m_lastGCLength);
+
+ StorageStatistics storageStatistics;
+ m_objectSpace.forEachLiveCell(storageStatistics);
+ dataLog("wasted .property storage: %ldkB (%ld%%)\n",
+ static_cast<long>(
+ (storageStatistics.storageCapacity() - storageStatistics.storageSize()) / KB),
+ static_cast<long>(
+ (storageStatistics.storageCapacity() - storageStatistics.storageSize()) * 100
+ / storageStatistics.storageCapacity()));
+ dataLog("objects with out-of-line .property storage: %ld (%ld%%)\n",
+ static_cast<long>(
+ storageStatistics.objectWithOutOfLineStorageCount()),
+ static_cast<long>(
+ storageStatistics.objectWithOutOfLineStorageCount() * 100
+ / storageStatistics.objectCount()));
+}
+
+void Heap::markDeadObjects()
+{
+ m_objectSpace.forEachDeadCell<MarkObject>();
}
void Heap::setActivityCallback(GCActivityCallback* activityCallback)
@@ -844,18 +956,10 @@
lastChanceToFinalize();
}
-class ZombifyCellFunctor : public MarkedBlock::VoidFunctor {
+class Zombify : public MarkedBlock::VoidFunctor {
public:
- ZombifyCellFunctor(size_t cellSize)
- : m_cellSize(cellSize)
- {
- }
-
void operator()(JSCell* cell)
{
- if (Options::zombiesAreImmortal())
- MarkedBlock::blockFor(cell)->setMarked(cell);
-
void** current = reinterpret_cast<void**>(cell);
// We want to maintain zapped-ness because that's how we know if we've called
@@ -863,30 +967,17 @@
if (cell->isZapped())
current++;
- void* limit = static_cast<void*>(reinterpret_cast<char*>(cell) + m_cellSize);
+ void* limit = static_cast<void*>(reinterpret_cast<char*>(cell) + MarkedBlock::blockFor(cell)->cellSize());
for (; current < limit; current++)
*current = reinterpret_cast<void*>(0xbbadbeef);
}
-
-private:
- size_t m_cellSize;
-};
-
-class ZombifyBlockFunctor : public MarkedBlock::VoidFunctor {
-public:
- void operator()(MarkedBlock* block)
- {
- ZombifyCellFunctor functor(block->cellSize());
- block->forEachDeadCell(functor);
- }
};
void Heap::zombifyDeadObjects()
{
+ // Sweep now because destructors will crash once we're zombified.
m_objectSpace.sweep();
-
- ZombifyBlockFunctor functor;
- m_objectSpace.forEachBlock(functor);
+ m_objectSpace.forEachDeadCell<Zombify>();
}
} // namespace JSC