Changeset 115156 in webkit


Ignore:
Timestamp:
Apr 24, 2012 6:29:42 PM (12 years ago)
Author:
mhahnenberg@apple.com
Message:

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):

Location:
trunk/Source/JavaScriptCore
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/API/JSBase.cpp

    r114772 r115156  
    101101    APIEntryShim entryShim(exec, false);
    102102
    103     exec->globalData().heap.activityCallback()->didAbandonObjectGraph();
     103    exec->globalData().heap.reportAbandonedObjectGraph();
    104104}
    105105
  • trunk/Source/JavaScriptCore/API/JSContextRef.cpp

    r114511 r115156  
    150150    } else if (releasingGlobalObject) {
    151151        globalData.heap.activityCallback()->synchronize();
    152         globalData.heap.activityCallback()->didAbandonObjectGraph();
     152        globalData.heap.reportAbandonedObjectGraph();
    153153    }
    154154
  • trunk/Source/JavaScriptCore/ChangeLog

    r115151 r115156  
     12012-04-24  Mark Hahnenberg  <mhahnenberg@apple.com>
     2
     3        GC Activity Callback timer should be based on how much has been allocated since the last collection
     4        https://bugs.webkit.org/show_bug.cgi?id=84763
     5
     6        Reviewed by Geoffrey Garen.
     7
     8        The desired behavior for the GC timer is to collect at some point in the future,
     9        regardless of how little we've allocated. A secondary goal, which is almost if not
     10        as important, is for the timer to collect sooner if there is the potential to
     11        collect a greater amount of memory. Conversely, as we allocate more memory we'd
     12        like to reduce the delay to the next collection. If we're allocating quickly enough,
     13        the timer should be preempted in favor of a normal allocation-triggered collection.
     14        If allocation were to slow or stop, we'd like the timer to be able to opportunistically
     15        run a collection without us having to allocate to the hard limit set by the Heap.
     16
     17        This type of policy can be described in terms of the amount of CPU we are willing
     18        to dedicate to reclaim a single MB of memory. For example, we might be willing to
     19        dedicate 1% of our CPU to reclaim 1 MB. We base our CPU usage off of the length of
     20        the last collection, e.g. if our last collection took 1ms, we would want to wait about
     21        100ms before running another collection to reclaim 1 MB. These constants should be
     22        tune-able, e.g. 0.1% CPU = 1 MB vs. 1% CPU = 1 MB vs. 10% CPU = 1 MB.
     23
     24        * API/JSBase.cpp: Use the new reportAbandonedObjectGraph.
     25        (JSGarbageCollect):
     26        * API/JSContextRef.cpp: Ditto.
     27        * heap/Heap.cpp:
     28        (JSC::Heap::Heap):
     29        (JSC::Heap::reportAbandonedObjectGraph): Similar to reportExtraMemoryCost. Clients call
     30        this function to notify the Heap that some unknown number of JSC objects might have just
     31        been abandoned and are now garbage. The Heap might schedule a new collection timer based
     32        on this notification.
     33        (JSC):
     34        (JSC::Heap::collect): Renamed m_lastFullGCSize to the less confusing m_sizeAfterLastCollect.
     35        * heap/Heap.h:
     36        (Heap):
     37        * heap/MarkedAllocator.h:
     38        (JSC::MarkedAllocator::zapFreeList): Fixed a bug in zapFreeList that failed to nullify the
     39        current allocator's FreeList once zapping was complete.
     40        * runtime/GCActivityCallback.cpp: Removed didAbandonObjectGraph because it was replaced by
     41        Heap::reportAbandonedObjectGraph.
     42        (JSC):
     43        * runtime/GCActivityCallback.h:
     44        (JSC::GCActivityCallback::willCollect):
     45        (DefaultGCActivityCallback):
     46        * runtime/GCActivityCallbackCF.cpp: Refactored the GC timer code so that we now schedule the
     47        timer based on how much we have allocated since the last collection up to a certain amount.
     48        We use the length of the previous GC to try to keep our total cost of opportunistic timer-triggered
     49        collections around 1% of the CPU per MB of garbage we expect to reclaim up to a maximum of 5 MB.
     50        (DefaultGCActivityCallbackPlatformData):
     51        (JSC):
     52        (JSC::DefaultGCActivityCallback::~DefaultGCActivityCallback):
     53        (JSC::DefaultGCActivityCallback::commonConstructor):
     54        (JSC::scheduleTimer):
     55        (JSC::cancelTimer):
     56        (JSC::DefaultGCActivityCallback::didAllocate):
     57
    1582012-04-24  Michael Saboff  <msaboff@apple.com>
    259
  • trunk/Source/JavaScriptCore/heap/Heap.cpp

    r115092 r115156  
    313313    : m_heapSize(heapSize)
    314314    , m_minBytesPerCycle(heapSizeForHint(heapSize))
    315     , m_lastFullGCSize(0)
     315    , m_sizeAfterLastCollect(0)
    316316    , m_bytesAllocatedLimit(m_minBytesPerCycle)
    317317    , m_bytesAllocated(0)
     
    472472}
    473473
     474void Heap::reportAbandonedObjectGraph()
     475{
     476    // Our clients don't know exactly how much memory they
     477    // are abandoning so we just guess for them.
     478    double abandonedBytes = 0.10 * m_sizeAfterLastCollect;
     479
     480    // We want to accelerate the next collection. Because memory has just
     481    // been abandoned, the next collection has the potential to
     482    // be more profitable. Since allocation is the trigger for collection,
     483    // we hasten the next collection by pretending that we've allocated more memory.
     484    didAllocate(abandonedBytes);
     485}
     486
    474487void Heap::protect(JSValue k)
    475488{
     
    813826    bool fullGC = sweepToggle == DoSweep;
    814827    if (!fullGC)
    815         fullGC = (capacity() > 4 * m_lastFullGCSize); 
     828        fullGC = (capacity() > 4 * m_sizeAfterLastCollect); 
    816829#else
    817830    bool fullGC = true;
     
    862875    size_t newSize = size();
    863876    if (fullGC) {
    864         m_lastFullGCSize = newSize;
     877        m_sizeAfterLastCollect = newSize;
    865878        m_bytesAllocatedLimit = max(newSize, m_minBytesPerCycle);
    866879    }
  • trunk/Source/JavaScriptCore/heap/Heap.h

    r115092 r115156  
    118118        bool shouldCollect();
    119119        void collect(SweepToggle);
     120
    120121        void reportExtraMemoryCost(size_t cost);
     122        void reportAbandonedObjectGraph();
    121123
    122124        JS_EXPORT_PRIVATE void protect(JSValue);
     
    204206        const HeapSize m_heapSize;
    205207        const size_t m_minBytesPerCycle;
    206         size_t m_lastFullGCSize;
     208        size_t m_sizeAfterLastCollect;
    207209
    208210        size_t m_bytesAllocatedLimit;
  • trunk/Source/JavaScriptCore/heap/MarkedAllocator.h

    r114698 r115156  
    9292   
    9393    m_currentBlock->zapFreeList(m_freeList);
    94     m_freeList.head = 0;
     94    m_freeList = MarkedBlock::FreeList();
    9595}
    9696
  • trunk/Source/JavaScriptCore/runtime/GCActivityCallback.cpp

    r114772 r115156  
    5151}
    5252
    53 void DefaultGCActivityCallback::didAbandonObjectGraph()
    54 {
    55 }
    56 
    5753void DefaultGCActivityCallback::synchronize()
    5854{
  • trunk/Source/JavaScriptCore/runtime/GCActivityCallback.h

    r114772 r115156  
    4646    virtual void didAllocate(size_t) { }
    4747    virtual void willCollect() { }
    48     virtual void didAbandonObjectGraph() { }
    4948    virtual void synchronize() { }
    5049
     
    6463    virtual void didAllocate(size_t);
    6564    virtual void willCollect();
    66     virtual void didAbandonObjectGraph();
    6765    virtual void synchronize();
    6866
  • trunk/Source/JavaScriptCore/runtime/GCActivityCallbackCF.cpp

    r114772 r115156  
    5151    RetainPtr<CFRunLoopRef> runLoop;
    5252    CFRunLoopTimerContext context;
    53     bool timerIsActive;
     53    double delay;
    5454};
    5555
    56 const double gcCPUBudget = 0.025;
    57 const double gcTimerIntervalMultiplier = 1.0 / gcCPUBudget;
     56const double gcTimeSlicePerMB = 0.01; // Percentage of CPU time we will spend to reclaim 1 MB
     57const double maxGCTimeSlice = 0.05; // The maximum amount of CPU time we want to use for opportunistic timer-triggered collections.
     58const double timerSlop = 2.0; // Fudge factor to avoid performance cost of resetting timer.
    5859const CFTimeInterval decade = 60 * 60 * 24 * 365 * 10;
    5960const CFTimeInterval hour = 60 * 60;
    60 const size_t minBytesBeforeCollect = 128 * KB;
    6161
    6262void DefaultGCActivityCallbackPlatformData::timerDidFire(CFRunLoopTimerRef, void *info)
     
    8181    CFRunLoopRemoveTimer(d->runLoop.get(), d->timer.get(), kCFRunLoopCommonModes);
    8282    CFRunLoopTimerInvalidate(d->timer.get());
    83     d->context.info = 0;
    84     d->runLoop = 0;
    85     d->timer = 0;
    8683}
    8784
     
    9491    d->runLoop = runLoop;
    9592    d->timer.adoptCF(CFRunLoopTimerCreate(0, decade, decade, 0, 0, DefaultGCActivityCallbackPlatformData::timerDidFire, &d->context));
    96     d->timerIsActive = false;
     93    d->delay = decade;
    9794    CFRunLoopAddTimer(d->runLoop.get(), d->timer.get(), kCFRunLoopCommonModes);
    9895}
    9996
    100 static void scheduleTimer(DefaultGCActivityCallbackPlatformData* d)
     97static void scheduleTimer(DefaultGCActivityCallbackPlatformData* d, double newDelay)
    10198{
    102     if (d->timerIsActive)
     99    if (newDelay * timerSlop > d->delay)
    103100        return;
    104     d->timerIsActive = true;
    105     CFTimeInterval triggerInterval = static_cast<Heap*>(d->context.info)->lastGCLength() * gcTimerIntervalMultiplier;
    106     CFRunLoopTimerSetNextFireDate(d->timer.get(), CFAbsoluteTimeGetCurrent() + triggerInterval);
     101    double delta = d->delay - newDelay;
     102    d->delay = newDelay;
     103    CFRunLoopTimerSetNextFireDate(d->timer.get(), CFRunLoopTimerGetNextFireDate(d->timer.get()) - delta);
    107104}
    108105
    109106static void cancelTimer(DefaultGCActivityCallbackPlatformData* d)
    110107{
    111     if (!d->timerIsActive)
    112         return;
    113     d->timerIsActive = false;
     108    d->delay = decade;
    114109    CFRunLoopTimerSetNextFireDate(d->timer.get(), CFAbsoluteTimeGetCurrent() + decade);
    115110}
     
    117112void DefaultGCActivityCallback::didAllocate(size_t bytes)
    118113{
    119     if (bytes < minBytesBeforeCollect)
    120         return;
    121     scheduleTimer(d.get());
     114    // The first byte allocated in an allocation cycle will report 0 bytes to didAllocate.
     115    // We pretend it's one byte so that we don't ignore this allocation entirely.
     116    if (!bytes)
     117        bytes = 1;
     118    Heap* heap = static_cast<Heap*>(d->context.info);
     119    double gcTimeSlice = std::min((static_cast<double>(bytes) / MB) * gcTimeSlicePerMB, maxGCTimeSlice);
     120    double newDelay = heap->lastGCLength() / gcTimeSlice;
     121    scheduleTimer(d.get(), newDelay);
    122122}
    123123
     
    125125{
    126126    cancelTimer(d.get());
    127 }
    128 
    129 void DefaultGCActivityCallback::didAbandonObjectGraph()
    130 {
    131     scheduleTimer(d.get());
    132127}
    133128
Note: See TracChangeset for help on using the changeset viewer.