Commit def139e9 authored by ggaren@apple.com's avatar ggaren@apple.com
Browse files

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
parent 4719c49b
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
......
......@@ -176,6 +176,15 @@ static inline bool isValidThreadState(JSGlobalData* globalData)
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 @@ inline PassOwnPtr<TypeCountSet> RecordType::returnValue()
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 @@ void Heap::collect(SweepToggle sweepToggle)
m_objectSpace.resetAllocators();
}
if (Options::useZombieMode())
zombifyDeadObjects();
size_t currentHeapSize = size();
if (fullGC) {
m_sizeAfterLastCollect = currentHeapSize;
......@@ -769,10 +843,48 @@ void Heap::collect(SweepToggle sweepToggle)
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 @@ void Heap::didStartVMShutdown()
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 @@ public:
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
......@@ -145,6 +145,7 @@ namespace JSC {
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 @@ namespace JSC {
void finalizeUnconditionalFinalizers();
void deleteUnmarkedCompiledCode();
void zombifyDeadObjects();
void markDeadObjects();
RegisterFile& registerFile();
BlockAllocator& blockAllocator();
......
......@@ -95,6 +95,8 @@ public:
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 @@ template<typename Functor> inline typename Functor::ReturnType MarkedSpace::forE
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];
......
......@@ -127,6 +127,11 @@ void Options::initialize()
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 @@ void Options::initialize()
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());
......
......@@ -121,7 +121,8 @@ namespace JSC {
v(unsigned, forcedWeakRandomSeed, 0) \
\
v(bool, useZombieMode, false) \
v(bool, zombiesAreImmortal, false)
v(bool, objectsAreImmortal, false) \
v(bool, showHeapStatistics, false)
class Options {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment