GC Activity Callback timer should be based on how much has been allocated since the...
authormhahnenberg@apple.com <mhahnenberg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 25 Apr 2012 01:29:42 +0000 (01:29 +0000)
committermhahnenberg@apple.com <mhahnenberg@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 25 Apr 2012 01:29:42 +0000 (01:29 +0000)
https://bugs.webkit.org/show_bug.cgi?id=84763

Reviewed by Geoffrey Garen.

The desired behavior for the GC timer is to collect at some point in the future,
regardless of how little we've allocated. A secondary goal, which is almost if not
as important, is for the timer to collect sooner if there is the potential to
collect a greater amount of memory. Conversely, as we allocate more memory we'd
like to reduce the delay to the next collection. If we're allocating quickly enough,
the timer should be preempted in favor of a normal allocation-triggered collection.
If allocation were to slow or stop, we'd like the timer to be able to opportunistically
run a collection without us having to allocate to the hard limit set by the Heap.

This type of policy can be described in terms of the amount of CPU we are willing
to dedicate to reclaim a single MB of memory. For example, we might be willing to
dedicate 1% of our CPU to reclaim 1 MB. We base our CPU usage off of the length of
the last collection, e.g. if our last collection took 1ms, we would want to wait about
100ms before running another collection to reclaim 1 MB. These constants should be
tune-able, e.g. 0.1% CPU = 1 MB vs. 1% CPU = 1 MB vs. 10% CPU = 1 MB.

* API/JSBase.cpp: Use the new reportAbandonedObjectGraph.
(JSGarbageCollect):
* API/JSContextRef.cpp: Ditto.
* heap/Heap.cpp:
(JSC::Heap::Heap):
(JSC::Heap::reportAbandonedObjectGraph): Similar to reportExtraMemoryCost. Clients call
this function to notify the Heap that some unknown number of JSC objects might have just
been abandoned and are now garbage. The Heap might schedule a new collection timer based
on this notification.
(JSC):
(JSC::Heap::collect): Renamed m_lastFullGCSize to the less confusing m_sizeAfterLastCollect.
* heap/Heap.h:
(Heap):
* heap/MarkedAllocator.h:
(JSC::MarkedAllocator::zapFreeList): Fixed a bug in zapFreeList that failed to nullify the
current allocator's FreeList once zapping was complete.
* runtime/GCActivityCallback.cpp: Removed didAbandonObjectGraph because it was replaced by
Heap::reportAbandonedObjectGraph.
(JSC):
* runtime/GCActivityCallback.h:
(JSC::GCActivityCallback::willCollect):
(DefaultGCActivityCallback):
* runtime/GCActivityCallbackCF.cpp: Refactored the GC timer code so that we now schedule the
timer based on how much we have allocated since the last collection up to a certain amount.
We use the length of the previous GC to try to keep our total cost of opportunistic timer-triggered
collections around 1% of the CPU per MB of garbage we expect to reclaim up to a maximum of 5 MB.
(DefaultGCActivityCallbackPlatformData):
(JSC):
(JSC::DefaultGCActivityCallback::~DefaultGCActivityCallback):
(JSC::DefaultGCActivityCallback::commonConstructor):
(JSC::scheduleTimer):
(JSC::cancelTimer):
(JSC::DefaultGCActivityCallback::didAllocate):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@115156 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/API/JSBase.cpp
Source/JavaScriptCore/API/JSContextRef.cpp
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/heap/Heap.cpp
Source/JavaScriptCore/heap/Heap.h
Source/JavaScriptCore/heap/MarkedAllocator.h
Source/JavaScriptCore/runtime/GCActivityCallback.cpp
Source/JavaScriptCore/runtime/GCActivityCallback.h
Source/JavaScriptCore/runtime/GCActivityCallbackCF.cpp

index 53bcdbf..d0ffa31 100644 (file)
@@ -100,7 +100,7 @@ void JSGarbageCollect(JSContextRef ctx)
     ExecState* exec = toJS(ctx);
     APIEntryShim entryShim(exec, false);
 
-    exec->globalData().heap.activityCallback()->didAbandonObjectGraph();
+    exec->globalData().heap.reportAbandonedObjectGraph();
 }
 
 void JSReportExtraMemoryCost(JSContextRef ctx, size_t size)
index 5dbc01c..c318800 100644 (file)
@@ -149,7 +149,7 @@ void JSGlobalContextRelease(JSGlobalContextRef ctx)
         globalData.heap.destroy();
     } else if (releasingGlobalObject) {
         globalData.heap.activityCallback()->synchronize();
-        globalData.heap.activityCallback()->didAbandonObjectGraph();
+        globalData.heap.reportAbandonedObjectGraph();
     }
 
     globalData.deref();
index 9ed61ad..549d880 100644 (file)
@@ -1,3 +1,60 @@
+2012-04-24  Mark Hahnenberg  <mhahnenberg@apple.com>
+
+        GC Activity Callback timer should be based on how much has been allocated since the last collection
+        https://bugs.webkit.org/show_bug.cgi?id=84763
+
+        Reviewed by Geoffrey Garen.
+
+        The desired behavior for the GC timer is to collect at some point in the future, 
+        regardless of how little we've allocated. A secondary goal, which is almost if not 
+        as important, is for the timer to collect sooner if there is the potential to 
+        collect a greater amount of memory. Conversely, as we allocate more memory we'd 
+        like to reduce the delay to the next collection. If we're allocating quickly enough, 
+        the timer should be preempted in favor of a normal allocation-triggered collection. 
+        If allocation were to slow or stop, we'd like the timer to be able to opportunistically 
+        run a collection without us having to allocate to the hard limit set by the Heap.
+
+        This type of policy can be described in terms of the amount of CPU we are willing 
+        to dedicate to reclaim a single MB of memory. For example, we might be willing to 
+        dedicate 1% of our CPU to reclaim 1 MB. We base our CPU usage off of the length of 
+        the last collection, e.g. if our last collection took 1ms, we would want to wait about 
+        100ms before running another collection to reclaim 1 MB. These constants should be 
+        tune-able, e.g. 0.1% CPU = 1 MB vs. 1% CPU = 1 MB vs. 10% CPU = 1 MB.
+
+        * API/JSBase.cpp: Use the new reportAbandonedObjectGraph.
+        (JSGarbageCollect):
+        * API/JSContextRef.cpp: Ditto.
+        * heap/Heap.cpp:
+        (JSC::Heap::Heap):
+        (JSC::Heap::reportAbandonedObjectGraph): Similar to reportExtraMemoryCost. Clients call
+        this function to notify the Heap that some unknown number of JSC objects might have just 
+        been abandoned and are now garbage. The Heap might schedule a new collection timer based 
+        on this notification.
+        (JSC):
+        (JSC::Heap::collect): Renamed m_lastFullGCSize to the less confusing m_sizeAfterLastCollect.
+        * heap/Heap.h:
+        (Heap):
+        * heap/MarkedAllocator.h:
+        (JSC::MarkedAllocator::zapFreeList): Fixed a bug in zapFreeList that failed to nullify the 
+        current allocator's FreeList once zapping was complete.
+        * runtime/GCActivityCallback.cpp: Removed didAbandonObjectGraph because it was replaced by 
+        Heap::reportAbandonedObjectGraph.
+        (JSC):
+        * runtime/GCActivityCallback.h:
+        (JSC::GCActivityCallback::willCollect):
+        (DefaultGCActivityCallback):
+        * runtime/GCActivityCallbackCF.cpp: Refactored the GC timer code so that we now schedule the 
+        timer based on how much we have allocated since the last collection up to a certain amount. 
+        We use the length of the previous GC to try to keep our total cost of opportunistic timer-triggered
+        collections around 1% of the CPU per MB of garbage we expect to reclaim up to a maximum of 5 MB.
+        (DefaultGCActivityCallbackPlatformData):
+        (JSC):
+        (JSC::DefaultGCActivityCallback::~DefaultGCActivityCallback):
+        (JSC::DefaultGCActivityCallback::commonConstructor):
+        (JSC::scheduleTimer):
+        (JSC::cancelTimer):
+        (JSC::DefaultGCActivityCallback::didAllocate):
+
 2012-04-24  Michael Saboff  <msaboff@apple.com>
 
         objectProtoFuncToString creates new string every invocation
index 71c1eab..344418b 100644 (file)
@@ -312,7 +312,7 @@ inline PassOwnPtr<TypeCountSet> RecordType::returnValue()
 Heap::Heap(JSGlobalData* globalData, HeapSize heapSize)
     : m_heapSize(heapSize)
     , m_minBytesPerCycle(heapSizeForHint(heapSize))
-    , m_lastFullGCSize(0)
+    , m_sizeAfterLastCollect(0)
     , m_bytesAllocatedLimit(m_minBytesPerCycle)
     , m_bytesAllocated(0)
     , m_operationInProgress(NoOperation)
@@ -471,6 +471,19 @@ void Heap::reportExtraMemoryCostSlowCase(size_t cost)
         collect(DoNotSweep);
 }
 
+void Heap::reportAbandonedObjectGraph()
+{
+    // Our clients don't know exactly how much memory they
+    // are abandoning so we just guess for them.
+    double abandonedBytes = 0.10 * m_sizeAfterLastCollect;
+
+    // We want to accelerate the next collection. Because memory has just 
+    // been abandoned, the next collection has the potential to 
+    // be more profitable. Since allocation is the trigger for collection, 
+    // we hasten the next collection by pretending that we've allocated more memory. 
+    didAllocate(abandonedBytes);
+}
+
 void Heap::protect(JSValue k)
 {
     ASSERT(k);
@@ -812,7 +825,7 @@ void Heap::collect(SweepToggle sweepToggle)
 #if ENABLE(GGC)
     bool fullGC = sweepToggle == DoSweep;
     if (!fullGC)
-        fullGC = (capacity() > 4 * m_lastFullGCSize);  
+        fullGC = (capacity() > 4 * m_sizeAfterLastCollect);  
 #else
     bool fullGC = true;
 #endif
@@ -861,7 +874,7 @@ void Heap::collect(SweepToggle sweepToggle)
     // new bytes allocated) proportion, and seems to work well in benchmarks.
     size_t newSize = size();
     if (fullGC) {
-        m_lastFullGCSize = newSize;
+        m_sizeAfterLastCollect = newSize;
         m_bytesAllocatedLimit = max(newSize, m_minBytesPerCycle);
     }
     m_bytesAllocated = 0;
index e0b9028..7040908 100644 (file)
@@ -117,7 +117,9 @@ namespace JSC {
         enum SweepToggle { DoNotSweep, DoSweep };
         bool shouldCollect();
         void collect(SweepToggle);
+
         void reportExtraMemoryCost(size_t cost);
+        void reportAbandonedObjectGraph();
 
         JS_EXPORT_PRIVATE void protect(JSValue);
         JS_EXPORT_PRIVATE bool unprotect(JSValue); // True when the protect count drops to 0.
@@ -203,7 +205,7 @@ namespace JSC {
         
         const HeapSize m_heapSize;
         const size_t m_minBytesPerCycle;
-        size_t m_lastFullGCSize;
+        size_t m_sizeAfterLastCollect;
 
         size_t m_bytesAllocatedLimit;
         size_t m_bytesAllocated;
index e2f4af0..d603455 100644 (file)
@@ -91,7 +91,7 @@ inline void MarkedAllocator::zapFreeList()
     }
     
     m_currentBlock->zapFreeList(m_freeList);
-    m_freeList.head = 0;
+    m_freeList = MarkedBlock::FreeList();
 }
 
 template <typename Functor> inline void MarkedAllocator::forEachBlock(Functor& functor)
index a27f0b8..a63c489 100644 (file)
@@ -50,10 +50,6 @@ void DefaultGCActivityCallback::willCollect()
 {
 }
 
-void DefaultGCActivityCallback::didAbandonObjectGraph()
-{
-}
-
 void DefaultGCActivityCallback::synchronize()
 {
 }
index e61e7b1..ba8ca96 100644 (file)
@@ -45,7 +45,6 @@ public:
     virtual ~GCActivityCallback() { }
     virtual void didAllocate(size_t) { }
     virtual void willCollect() { }
-    virtual void didAbandonObjectGraph() { }
     virtual void synchronize() { }
 
 protected:
@@ -63,7 +62,6 @@ public:
 
     virtual void didAllocate(size_t);
     virtual void willCollect();
-    virtual void didAbandonObjectGraph();
     virtual void synchronize();
 
 #if USE(CF)
index d8dd711..a2c73bc 100644 (file)
@@ -50,14 +50,14 @@ struct DefaultGCActivityCallbackPlatformData {
     RetainPtr<CFRunLoopTimerRef> timer;
     RetainPtr<CFRunLoopRef> runLoop;
     CFRunLoopTimerContext context;
-    bool timerIsActive;
+    double delay;
 };
 
-const double gcCPUBudget = 0.025;
-const double gcTimerIntervalMultiplier = 1.0 / gcCPUBudget;
+const double gcTimeSlicePerMB = 0.01; // Percentage of CPU time we will spend to reclaim 1 MB
+const double maxGCTimeSlice = 0.05; // The maximum amount of CPU time we want to use for opportunistic timer-triggered collections.
+const double timerSlop = 2.0; // Fudge factor to avoid performance cost of resetting timer.
 const CFTimeInterval decade = 60 * 60 * 24 * 365 * 10;
 const CFTimeInterval hour = 60 * 60;
-const size_t minBytesBeforeCollect = 128 * KB;
 
 void DefaultGCActivityCallbackPlatformData::timerDidFire(CFRunLoopTimerRef, void *info)
 {
@@ -80,9 +80,6 @@ DefaultGCActivityCallback::~DefaultGCActivityCallback()
 {
     CFRunLoopRemoveTimer(d->runLoop.get(), d->timer.get(), kCFRunLoopCommonModes);
     CFRunLoopTimerInvalidate(d->timer.get());
-    d->context.info = 0;
-    d->runLoop = 0;
-    d->timer = 0;
 }
 
 void DefaultGCActivityCallback::commonConstructor(Heap* heap, CFRunLoopRef runLoop)
@@ -93,32 +90,35 @@ void DefaultGCActivityCallback::commonConstructor(Heap* heap, CFRunLoopRef runLo
     d->context.info = heap;
     d->runLoop = runLoop;
     d->timer.adoptCF(CFRunLoopTimerCreate(0, decade, decade, 0, 0, DefaultGCActivityCallbackPlatformData::timerDidFire, &d->context));
-    d->timerIsActive = false;
+    d->delay = decade;
     CFRunLoopAddTimer(d->runLoop.get(), d->timer.get(), kCFRunLoopCommonModes);
 }
 
-static void scheduleTimer(DefaultGCActivityCallbackPlatformData* d)
+static void scheduleTimer(DefaultGCActivityCallbackPlatformData* d, double newDelay)
 {
-    if (d->timerIsActive)
+    if (newDelay * timerSlop > d->delay)
         return;
-    d->timerIsActive = true;
-    CFTimeInterval triggerInterval = static_cast<Heap*>(d->context.info)->lastGCLength() * gcTimerIntervalMultiplier; 
-    CFRunLoopTimerSetNextFireDate(d->timer.get(), CFAbsoluteTimeGetCurrent() + triggerInterval);
+    double delta = d->delay - newDelay;
+    d->delay = newDelay;
+    CFRunLoopTimerSetNextFireDate(d->timer.get(), CFRunLoopTimerGetNextFireDate(d->timer.get()) - delta);
 }
 
 static void cancelTimer(DefaultGCActivityCallbackPlatformData* d)
 {
-    if (!d->timerIsActive)
-        return;
-    d->timerIsActive = false;
+    d->delay = decade;
     CFRunLoopTimerSetNextFireDate(d->timer.get(), CFAbsoluteTimeGetCurrent() + decade);
 }
 
 void DefaultGCActivityCallback::didAllocate(size_t bytes)
 {
-    if (bytes < minBytesBeforeCollect)
-        return;
-    scheduleTimer(d.get());
+    // The first byte allocated in an allocation cycle will report 0 bytes to didAllocate. 
+    // We pretend it's one byte so that we don't ignore this allocation entirely.
+    if (!bytes)
+        bytes = 1;
+    Heap* heap = static_cast<Heap*>(d->context.info);
+    double gcTimeSlice = std::min((static_cast<double>(bytes) / MB) * gcTimeSlicePerMB, maxGCTimeSlice);
+    double newDelay = heap->lastGCLength() / gcTimeSlice;
+    scheduleTimer(d.get(), newDelay);
 }
 
 void DefaultGCActivityCallback::willCollect()
@@ -126,11 +126,6 @@ void DefaultGCActivityCallback::willCollect()
     cancelTimer(d.get());
 }
 
-void DefaultGCActivityCallback::didAbandonObjectGraph()
-{
-    scheduleTimer(d.get());
-}
-
 void DefaultGCActivityCallback::synchronize()
 {
     if (CFRunLoopGetCurrent() == d->runLoop.get())