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/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 32d5897..bf71bcd 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,58 @@
+2012-09-25 Geoffrey Garen <ggaren@apple.com>
+
+ 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.
+
2012-09-25 Filip Pizlo <fpizlo@apple.com>
We shouldn't use the optimized versions of shift/unshift if the user is doing crazy things to the array
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
diff --git a/Source/JavaScriptCore/heap/Heap.h b/Source/JavaScriptCore/heap/Heap.h
index c9bbec1..92efff7 100644
--- a/Source/JavaScriptCore/heap/Heap.h
+++ b/Source/JavaScriptCore/heap/Heap.h
@@ -145,6 +145,7 @@
JS_EXPORT_PRIVATE size_t protectedGlobalObjectCount();
JS_EXPORT_PRIVATE PassOwnPtr<TypeCountSet> protectedObjectTypeCounts();
JS_EXPORT_PRIVATE PassOwnPtr<TypeCountSet> objectTypeCounts();
+ void showStatistics();
void pushTempSortVector(Vector<ValueStringPair>*);
void popTempSortVector(Vector<ValueStringPair>*);
@@ -205,7 +206,8 @@
void finalizeUnconditionalFinalizers();
void deleteUnmarkedCompiledCode();
void zombifyDeadObjects();
-
+ void markDeadObjects();
+
RegisterFile& registerFile();
BlockAllocator& blockAllocator();
diff --git a/Source/JavaScriptCore/heap/MarkedSpace.h b/Source/JavaScriptCore/heap/MarkedSpace.h
index e68ba91..151099b 100644
--- a/Source/JavaScriptCore/heap/MarkedSpace.h
+++ b/Source/JavaScriptCore/heap/MarkedSpace.h
@@ -95,6 +95,8 @@
template<typename Functor> typename Functor::ReturnType forEachLiveCell(Functor&);
template<typename Functor> typename Functor::ReturnType forEachLiveCell();
+ template<typename Functor> typename Functor::ReturnType forEachDeadCell(Functor&);
+ template<typename Functor> typename Functor::ReturnType forEachDeadCell();
template<typename Functor> typename Functor::ReturnType forEachBlock(Functor&);
template<typename Functor> typename Functor::ReturnType forEachBlock();
@@ -156,6 +158,22 @@
return forEachLiveCell(functor);
}
+template<typename Functor> inline typename Functor::ReturnType MarkedSpace::forEachDeadCell(Functor& functor)
+{
+ canonicalizeCellLivenessData();
+
+ BlockIterator end = m_blocks.set().end();
+ for (BlockIterator it = m_blocks.set().begin(); it != end; ++it)
+ (*it)->forEachDeadCell(functor);
+ return functor.returnValue();
+}
+
+template<typename Functor> inline typename Functor::ReturnType MarkedSpace::forEachDeadCell()
+{
+ Functor functor;
+ return forEachDeadCell(functor);
+}
+
inline MarkedAllocator& MarkedSpace::firstAllocator()
{
return m_normalSpace.preciseAllocators[0];
diff --git a/Source/JavaScriptCore/runtime/Options.cpp b/Source/JavaScriptCore/runtime/Options.cpp
index b164948..ed0720b 100644
--- a/Source/JavaScriptCore/runtime/Options.cpp
+++ b/Source/JavaScriptCore/runtime/Options.cpp
@@ -127,6 +127,11 @@
JSC_OPTIONS(FOR_EACH_OPTION)
#undef FOR_EACH_OPTION
+#if USE(CF) || OS(UNIX)
+ objectsAreImmortal() = !!getenv("JSImmortalZombieEnabled");
+ useZombieMode() = !!getenv("JSImmortalZombieEnabled") || !!getenv("JSZombieEnabled");
+#endif
+
// Allow environment vars to override options if applicable.
// The evn var should be the name of the option prefixed with
// "JSC_".
@@ -149,11 +154,6 @@
useRegExpJIT() = false;
#endif
-#if USE(CF) || OS(UNIX)
- zombiesAreImmortal() = !!getenv("JSImmortalZombieEnabled");
- useZombieMode() = zombiesAreImmortal() || !!getenv("JSZombieEnabled");
-#endif
-
// Do range checks where needed and make corrections to the options:
ASSERT(thresholdForOptimizeAfterLongWarmUp() >= thresholdForOptimizeAfterWarmUp());
ASSERT(thresholdForOptimizeAfterWarmUp() >= thresholdForOptimizeSoon());
diff --git a/Source/JavaScriptCore/runtime/Options.h b/Source/JavaScriptCore/runtime/Options.h
index 5e53d1c..7571f91 100644
--- a/Source/JavaScriptCore/runtime/Options.h
+++ b/Source/JavaScriptCore/runtime/Options.h
@@ -121,7 +121,8 @@
v(unsigned, forcedWeakRandomSeed, 0) \
\
v(bool, useZombieMode, false) \
- v(bool, zombiesAreImmortal, false)
+ v(bool, objectsAreImmortal, false) \
+ v(bool, showHeapStatistics, false)
class Options {