Changeset 241351 in webkit


Ignore:
Timestamp:
Feb 13, 2019 12:41:22 AM (5 years ago)
Author:
benjamin@webkit.org
Message:

Responsiveness timers are too expensive for frequent events
https://bugs.webkit.org/show_bug.cgi?id=194003

Reviewed by Geoffrey Garen.

With each event, we set a responsivness timer to check if the WebProcess
is responsive, and reset the timer when the WebProcess sends an answer.

For frequent events (e.g. wheel events, mouse force events, etc),
we are spamming the kernel with hundreds of timers per second.
That is a bit inefficient.

Another source of inefficiency comes from the timer implementation
itself. Stopping a RunLoop::Timer removes the timer from every mode
and invalidate the timer. It becomes costly since we do it a lot.

With this patch, I tweak ResponsivenessTimer and its use to minimize
how often we schedule system timers.

The first change is to not stop the timer when we get the stop()
calls if we expect more events to come in. Instead, we keep track
if we care about the timeout or not in the attribute "m_waitingForTimer".
When the next event starts, we can reschedule the timer without ever
having told the kernel about the stop.
If there are no next events, the timeout fires but m_waitingForTimer
is false. To avoid idle wake up, the lazy stop is only used when having
following events is common.

The second improvements comes from not even rescheduling the timer
when restarted. Instead of changing the timer, we let the original timer
fire and re-shedule a new one with the missing time.

For more context, also see patches r240759 and r240944.

  • UIProcess/ResponsivenessTimer.cpp:

(WebKit::ResponsivenessTimer::ResponsivenessTimer):
(WebKit::ResponsivenessTimer::invalidate):
(WebKit::ResponsivenessTimer::timerFired):
(WebKit::ResponsivenessTimer::start):
(WebKit::ResponsivenessTimer::startWithLazyStop):
(WebKit::ResponsivenessTimer::stop):
(WebKit::ResponsivenessTimer::processTerminated):
(WebKit::ResponsivenessTimer::~ResponsivenessTimer): Deleted.

  • UIProcess/ResponsivenessTimer.h:

(WebKit::ResponsivenessTimer::hasActiveTimer const):

  • UIProcess/WebPageProxy.cpp:

(WebKit::WebPageProxy::processNextQueuedMouseEvent):
(WebKit::WebPageProxy::sendWheelEvent):
(WebKit::WebPageProxy::handleKeyboardEvent):
(WebKit::WebPageProxy::handleGestureEvent):

  • UIProcess/WebProcessProxy.cpp:

(WebKit::WebProcessProxy::isResponsiveWithLazyStop):

  • UIProcess/WebProcessProxy.h:
Location:
trunk/Source/WebKit
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebKit/ChangeLog

    r241349 r241351  
     12019-02-13  Benjamin Poulain  <benjamin@webkit.org>
     2
     3        Responsiveness timers are too expensive for frequent events
     4        https://bugs.webkit.org/show_bug.cgi?id=194003
     5
     6        Reviewed by Geoffrey Garen.
     7
     8        With each event, we set a responsivness timer to check if the WebProcess
     9        is responsive, and reset the timer when the WebProcess sends an answer.
     10
     11        For frequent events (e.g. wheel events, mouse force events, etc),
     12        we are spamming the kernel with hundreds of timers per second.
     13        That is a bit inefficient.
     14
     15        Another source of inefficiency comes from the timer implementation
     16        itself. Stopping a RunLoop::Timer removes the timer from every mode
     17        and invalidate the timer. It becomes costly since we do it a lot.
     18
     19        With this patch, I tweak ResponsivenessTimer and its use to minimize
     20        how often we schedule system timers.
     21
     22        The first change is to not stop the timer when we get the stop()
     23        calls if we expect more events to come in. Instead, we keep track
     24        if we care about the timeout or not in the attribute "m_waitingForTimer".
     25        When the next event starts, we can reschedule the timer without ever
     26        having told the kernel about the stop.
     27        If there are no next events, the timeout fires but m_waitingForTimer
     28        is false. To avoid idle wake up, the lazy stop is only used when having
     29        following events is common.
     30
     31        The second improvements comes from not even rescheduling the timer
     32        when restarted. Instead of changing the timer, we let the original timer
     33        fire and re-shedule a new one with the missing time.
     34
     35        For more context, also see patches r240759 and r240944.
     36
     37        * UIProcess/ResponsivenessTimer.cpp:
     38        (WebKit::ResponsivenessTimer::ResponsivenessTimer):
     39        (WebKit::ResponsivenessTimer::invalidate):
     40        (WebKit::ResponsivenessTimer::timerFired):
     41        (WebKit::ResponsivenessTimer::start):
     42        (WebKit::ResponsivenessTimer::startWithLazyStop):
     43        (WebKit::ResponsivenessTimer::stop):
     44        (WebKit::ResponsivenessTimer::processTerminated):
     45        (WebKit::ResponsivenessTimer::~ResponsivenessTimer): Deleted.
     46        * UIProcess/ResponsivenessTimer.h:
     47        (WebKit::ResponsivenessTimer::hasActiveTimer const):
     48        * UIProcess/WebPageProxy.cpp:
     49        (WebKit::WebPageProxy::processNextQueuedMouseEvent):
     50        (WebKit::WebPageProxy::sendWheelEvent):
     51        (WebKit::WebPageProxy::handleKeyboardEvent):
     52        (WebKit::WebPageProxy::handleGestureEvent):
     53        * UIProcess/WebProcessProxy.cpp:
     54        (WebKit::WebProcessProxy::isResponsiveWithLazyStop):
     55        * UIProcess/WebProcessProxy.h:
     56
    1572019-02-12  Tim Horton  <timothy_horton@apple.com>
    258
  • trunk/Source/WebKit/UIProcess/ResponsivenessTimer.cpp

    r241113 r241351  
    3333ResponsivenessTimer::ResponsivenessTimer(ResponsivenessTimer::Client& client)
    3434    : m_client(client)
    35     , m_isResponsive(true)
    3635    , m_timer(RunLoop::main(), this, &ResponsivenessTimer::timerFired)
    3736{
    3837}
    3938
    40 ResponsivenessTimer::~ResponsivenessTimer()
    41 {
    42     m_timer.stop();
    43 }
     39ResponsivenessTimer::~ResponsivenessTimer() = default;
    4440
    4541void ResponsivenessTimer::invalidate()
    4642{
    4743    m_timer.stop();
     44    m_restartFireTime = MonotonicTime();
     45    m_waitingForTimer = false;
     46    m_useLazyStop = false;
    4847}
    4948
    5049void ResponsivenessTimer::timerFired()
    5150{
     51    if (!m_waitingForTimer)
     52        return;
     53
     54    if (m_restartFireTime) {
     55        MonotonicTime now = MonotonicTime::now();
     56        MonotonicTime restartFireTime = m_restartFireTime;
     57        m_restartFireTime = MonotonicTime();
     58
     59        if (restartFireTime > now) {
     60            m_timer.startOneShot(now - restartFireTime);
     61            return;
     62        }
     63    }
     64
     65    m_waitingForTimer = false;
     66    m_useLazyStop = false;
     67
    5268    if (!m_isResponsive)
    5369        return;
    5470
    5571    if (!m_client.mayBecomeUnresponsive()) {
     72        m_waitingForTimer = true;
    5673        m_timer.startOneShot(responsivenessTimeout);
    5774        return;
     
    6784void ResponsivenessTimer::start()
    6885{
    69     if (m_timer.isActive())
     86    if (m_waitingForTimer)
    7087        return;
    7188
    72     m_timer.startOneShot(responsivenessTimeout);
     89    m_waitingForTimer = true;
     90    m_useLazyStop = false;
     91
     92    if (m_timer.isActive()) {
     93        // The timer is still active from a lazy stop.
     94        // Instead of restarting the timer, we schedule a new delay after this one finishes.
     95        //
     96        // In most cases, stop is called before we get to schedule the second timer, saving us
     97        // the scheduling of the timer entirely.
     98        m_restartFireTime = MonotonicTime::now() + responsivenessTimeout;
     99    } else {
     100        m_restartFireTime = MonotonicTime();
     101        m_timer.startOneShot(responsivenessTimeout);
     102    }
     103}
     104
     105void ResponsivenessTimer::startWithLazyStop()
     106{
     107    if (!m_waitingForTimer) {
     108        start();
     109        m_useLazyStop = true;
     110    }
    73111}
    74112
     
    84122    }
    85123
    86     m_timer.stop();
     124    m_waitingForTimer = false;
     125
     126    if (m_useLazyStop)
     127        m_useLazyStop = false;
     128    else
     129        m_timer.stop();
    87130}
    88131
    89132void ResponsivenessTimer::processTerminated()
    90133{
    91     // Since there is no web process, we must not be waiting for it anymore.
    92     stop();
     134    invalidate();
    93135}
    94136
  • trunk/Source/WebKit/UIProcess/ResponsivenessTimer.h

    r241113 r241351  
    4747    explicit ResponsivenessTimer(ResponsivenessTimer::Client&);
    4848    ~ResponsivenessTimer();
    49    
     49
    5050    void start();
     51
     52    // A responsiveness timer with lazy stop does not stop the underlying system timer when stopped.
     53    // Instead, it ignores the timeout if stop() was already called.
     54    //
     55    // This exists to reduce the rate at which we reset the timer.
     56    //
     57    // With a non lazy timer, we may set a timer and reset it soon after because the process is responsive.
     58    // For events, this means reseting a timer 120 times/s for a 60 Hz event source.
     59    // By not reseting the timer when responsive, we cut that in half to 60 timeout changes.
     60    void startWithLazyStop();
     61
    5162    void stop();
    5263
    5364    void invalidate();
    54    
     65
     66    // Return true if stop() was not called betfore the responsiveness timeout.
    5567    bool isResponsive() const { return m_isResponsive; }
     68
     69    // Return true if there is an active timer. The state could be responsive or not.
     70    bool hasActiveTimer() const { return m_waitingForTimer; }
    5671
    5772    void processTerminated();
     
    6176
    6277    ResponsivenessTimer::Client& m_client;
    63     bool m_isResponsive;
    6478
    6579    RunLoop::Timer<ResponsivenessTimer> m_timer;
     80    MonotonicTime m_restartFireTime;
     81
     82    bool m_isResponsive { true };
     83    bool m_waitingForTimer { false };
     84    bool m_useLazyStop { false };
    6685};
    6786
  • trunk/Source/WebKit/UIProcess/WebPageProxy.cpp

    r241336 r241351  
    22332233
    22342234    const NativeWebMouseEvent& event = m_mouseEventQueue.first();
    2235    
     2235
    22362236    if (pageClient().windowIsFrontWindowUnderMouse(event))
    22372237        setToolTip(String());
    22382238
    2239     // NOTE: This does not start the responsiveness timer because mouse move should not indicate interaction.
    2240     if (event.type() != WebEvent::MouseMove)
     2239    WebEvent::Type eventType = event.type();
     2240    if (eventType == WebEvent::MouseDown || eventType == WebEvent::MouseForceChanged || eventType == WebEvent::MouseForceDown)
     2241        m_process->responsivenessTimer().startWithLazyStop();
     2242    else if (eventType != WebEvent::MouseMove) {
     2243        // NOTE: This does not start the responsiveness timer because mouse move should not indicate interaction.
    22412244        m_process->responsivenessTimer().start();
    2242 
    2243     LOG(MouseHandling, "UIProcess: sent mouse event %s (queue size %zu)", webMouseEventTypeString(event.type()), m_mouseEventQueue.size());
     2245    }
     2246
     2247    LOG(MouseHandling, "UIProcess: sent mouse event %s (queue size %zu)", webMouseEventTypeString(eventType), m_mouseEventQueue.size());
    22442248    m_process->send(Messages::WebPage::MouseEvent(event), m_pageID);
    22452249}
     
    23612365    // Manually ping the web process to check for responsiveness since our wheel
    23622366    // event will dispatch to a non-main thread, which always responds.
    2363     m_process->isResponsive(nullptr);
     2367    m_process->isResponsiveWithLazyStop();
    23642368}
    23652369
     
    23942398    m_keyEventQueue.append(event);
    23952399
    2396     m_process->responsivenessTimer().start();
     2400    ResponsivenessTimer& responsivenessTimer = m_process->responsivenessTimer();
     2401    if (event.type() == WebEvent::KeyDown)
     2402        responsivenessTimer.startWithLazyStop();
     2403    else
     2404        responsivenessTimer.start();
     2405
    23972406    if (m_keyEventQueue.size() == 1) { // Otherwise, sent from DidReceiveEvent message handler.
    23982407        LOG(KeyHandling, " UI process: sent keyEvent from handleKeyboardEvent");
     
    25562565    m_gestureEventQueue.append(event);
    25572566    // FIXME: Consider doing some coalescing here.
    2558     m_process->responsivenessTimer().start();
     2567
     2568    ResponsivenessTimer& responsivenessTimer = m_process->responsivenessTimer();
     2569    if (event.type() == WebEvent::GestureStart || event.type() == WebEvent::GestureChange)
     2570        responsivenessTimer.startWithLazyStop();
     2571    else
     2572        responsivenessTimer.start();
    25592573
    25602574    m_process->send(Messages::EventDispatcher::GestureEvent(m_pageID, event), 0);
  • trunk/Source/WebKit/UIProcess/WebProcessProxy.cpp

    r241232 r241351  
    11341134}
    11351135
     1136void WebProcessProxy::isResponsiveWithLazyStop()
     1137{
     1138    if (m_isResponsive == NoOrMaybe::No)
     1139        return;
     1140
     1141    if (!responsivenessTimer().hasActiveTimer()) {
     1142        // We do not send a ping if we are already waiting for the WebProcess.
     1143        // Spamming pings on a slow web process is not helpful.
     1144        responsivenessTimer().startWithLazyStop();
     1145        send(Messages::WebProcess::MainThreadPing(), 0);
     1146    }
     1147}
     1148
    11361149bool WebProcessProxy::isJITEnabled() const
    11371150{
  • trunk/Source/WebKit/UIProcess/WebProcessProxy.h

    r241232 r241351  
    203203
    204204    void isResponsive(WTF::Function<void(bool isWebProcessResponsive)>&&);
     205    void isResponsiveWithLazyStop();
    205206    void didReceiveMainThreadPing();
    206207    void didReceiveBackgroundResponsivenessPing();
Note: See TracChangeset for help on using the changeset viewer.