RESOLVED FIXED 188144
REGRESSION (r230817): Terrible performance when selecting text on Stash code review
https://bugs.webkit.org/show_bug.cgi?id=188144
Summary REGRESSION (r230817): Terrible performance when selecting text on Stash code ...
Wenson Hsieh
Reported 2018-07-29 00:51:28 PDT
Attachments
Patch (5.50 KB, patch)
2018-07-29 01:20 PDT, Wenson Hsieh
no flags
Patch (5.47 KB, patch)
2018-07-29 02:12 PDT, Wenson Hsieh
darin: review+
Archive of layout-test-results from ews206 for win-future (13.02 MB, application/zip)
2018-07-29 15:52 PDT, EWS Watchlist
no flags
Archive of layout-test-results from ews205 for win-future (12.97 MB, application/zip)
2018-07-29 18:51 PDT, EWS Watchlist
no flags
Address review feedback. (7.44 KB, patch)
2018-07-30 12:50 PDT, Wenson Hsieh
no flags
Address review feedback (v2) (5.45 KB, patch)
2018-07-30 13:17 PDT, Wenson Hsieh
no flags
Wenson Hsieh
Comment 1 2018-07-29 01:20:43 PDT
Wenson Hsieh
Comment 2 2018-07-29 02:12:36 PDT
Simon Fraser (smfr)
Comment 3 2018-07-29 09:01:30 PDT
Comment on attachment 346024 [details] Patch Is there a danger here that the event replacement in a queue of mixed mouseMoved and mouseForceChanged will cause an earlier event to effectively get the coordinates of a later event, meaning that with rapid mouse movement, you'll make the event coordinates appear to jump around erratically?
Wenson Hsieh
Comment 4 2018-07-29 11:14:00 PDT
So this mechanism works by finding an event in the queue that can be replaced by the incoming event, removing that enqueued event, and then appending the incoming event to the end of the queue. The effect this should have is skipping over some events that otherwise would have been processed, rather than shuffling around the order of events, so I don’t think there’s a risk of sending mouse event coordinates to the page out of order.
Alexey Proskuryakov
Comment 5 2018-07-29 11:30:17 PDT
Is there a way to check how this will affect drawing when using a touchpad? The check for queue being non-empty seems like it could result in coalescing too much.
Wenson Hsieh
Comment 6 2018-07-29 14:15:51 PDT
(In reply to Alexey Proskuryakov from comment #5) > Is there a way to check how this will affect drawing when using a touchpad? Are there touchpads for drawing that support force click? Or do you mean drawing using a trackpad that supports force click? I couldn't test the former (since I don't have any touch pads, and couldn't find any that support force clicking after a quick Amazon search). For the latter, I surveyed a few online sketching tools (https://sketch.io, https://www.autodraw.com, http://kleki.com, https://drawisland.com), and they all seem to be working as expected... > The check for queue being non-empty seems like it could result in coalescing > too much. Hm...I'm not quite sure I understand what you mean. Is the comment referring to the following condition? while (queue.size() > 1) { … } Note that because of the break statement, we'll only ever replace a single mouse event in the queue with the incoming event. This means mouse event coalescing should work exactly as it does currently on trunk.
EWS Watchlist
Comment 7 2018-07-29 15:52:00 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 8 2018-07-29 15:52:12 PDT Comment hidden (obsolete)
Alexey Proskuryakov
Comment 9 2018-07-29 17:06:18 PDT
I don't know the difference between a touchpad and a trackpad. And yes, the question applies to the trunk behavior too, which is only a few months old as I understand it.
Wenson Hsieh
Comment 10 2018-07-29 17:45:20 PDT
(In reply to Alexey Proskuryakov from comment #9) > I don't know the difference between a touchpad and a trackpad. By touchpad, I assumed you meant something like this: https://www.amazon.com/Wacom-Bamboo-Pen-and-Touch/dp/B002OOWC3S (as opposed to a trackpad, which comes built-in with MacBook Pro). > And yes, the question applies to the trunk behavior too, which is only a few months old as I understand it. From <https://trac.webkit.org/r231511>: > When mousemove events come in faster than they can be processed, we should coalesce > pending mousemoves that have not yet been sent to WebProcess. This has the effect of > processing the most recent mousemove location, which is the old behavior that regressed. Brian or Simon might be more familiar with the history here, but it seems that coalescing mousemove events that had not yet been dispatched to the web process was the preexisting behavior before macOS Mojave (refer to https://trac.webkit.org/export/230816/webkit/trunk/Source/WebKit/UIProcess/WebPageProxy.cpp and search for m_nextMouseMoveEvent). r230817 had regressed this, causing laggy mouse move behaviors, and r231511 later fixed this by restoring mouse move coalescing, but only in the case where the mouse is not down, or a force-click trackpad is not being used. And so the patch here restores the old (pre-Mojave) behavior in this one remaining case where the user is dragging with the mouse down using a force-click enabled trackpad.
EWS Watchlist
Comment 11 2018-07-29 18:50:48 PDT Comment hidden (obsolete)
EWS Watchlist
Comment 12 2018-07-29 18:51:00 PDT Comment hidden (obsolete)
Simon Fraser (smfr)
Comment 13 2018-07-30 09:20:39 PDT
(In reply to Wenson Hsieh from comment #4) > So this mechanism works by finding an event in the queue that can be > replaced by the incoming event, removing that enqueued event, and then > appending the incoming event to the end of the queue. > > The effect this should have is skipping over some events that otherwise > would have been processed, rather than shuffling around the order of events, > so I don’t think there’s a risk of sending mouse event coordinates to the > page out of order. Say you receive: move at 5,5 forceChange at 10,10 then you receive a move at 20,20 won't the queue then contain: move at 20,20 forceChange at 10,10 thus flipping the coordinates?
Wenson Hsieh
Comment 14 2018-07-30 09:23:12 PDT
(In reply to Simon Fraser (smfr) from comment #13) > (In reply to Wenson Hsieh from comment #4) > > So this mechanism works by finding an event in the queue that can be > > replaced by the incoming event, removing that enqueued event, and then > > appending the incoming event to the end of the queue. > > > > The effect this should have is skipping over some events that otherwise > > would have been processed, rather than shuffling around the order of events, > > so I don’t think there’s a risk of sending mouse event coordinates to the > > page out of order. > > Say you receive: > > move at 5,5 > forceChange at 10,10 > > then you receive a move at 20,20 > > won't the queue then contain: > move at 20,20 > forceChange at 10,10 > > thus flipping the coordinates? No. As I explained in the ChangeLog as well as the above comment, this mechanism works by finding an event in the queue that can be replaced by the incoming event, removing that enqueued event, and then appending the incoming event to the end of the queue. So this: move at 5,5 forceChange at 10,10 would become: forceChange at 10,10 move at 20,20 ...thereby skipping "move at 5,5".
Darin Adler
Comment 15 2018-07-30 09:30:08 PDT
Comment on attachment 346024 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=346024&action=review > Source/WebKit/UIProcess/WebPageProxy.cpp:1953 > + Vector<NativeWebMouseEvent, 1> eventsAfterReplacedEvent; The algorithm here is unnecessarily inefficient. We can find the event to replace by iterating the Deque without modifying the Deque. Then we can replace it in place. There is no need to remove all the events after it and then re-append them. This could be the function: auto it = queue.rbegin(); auto end = queue.rend(); // Must not merge with the first event in the deque, since it is already being dispatched. if (it != end) --end; for (; it != end; ++it) { auto type = it->type(); if (type == incomingEvent.type()) { LOG(MouseHandling, "UIProcess: replacing mouse event %s (queue size %zu)", webMouseEventTypeString(type), queue.size()); *it = incomingEvent; return; } if (type != WebEvent::MouseMove && type != WebEvent::MouseForceChanged) break; } LOG(MouseHandling, "UIProcess: enqueueing mouse event %s (queue size %zu)", webMouseEventTypeString(incomingEvent.type()), queue.size()); queue.append(incomingEvent); I left the logging in, although I think it makes the code a little confusing. > Source/WebKit/UIProcess/WebPageProxy.cpp:1966 > + auto lastEvent = queue.takeLast(); > + if (lastEvent.type() == incomingEvent.type()) { > + performedReplacement = true; > + break; > + } > + > + eventsAfterReplacedEvent.append(WTFMove(lastEvent)); Two thoughts: 1) Could use lastEventType here instead of lastEvent.type(). 2) Could write this a different way that I find clearer: if (lastEventType == incomingEvent.type()) { queue.removeLast(); performedReplacement = true; break; } eventsAfterReplacedEvent.append(queue.takeLast()); There’s no real reason to structure the code so that the takeLast is shared. But I like the rewritten version above even more -- replacing in place seems better.
Darin Adler
Comment 16 2018-07-30 09:32:02 PDT
Comment on attachment 346024 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=346024&action=review >> Source/WebKit/UIProcess/WebPageProxy.cpp:1953 >> + Vector<NativeWebMouseEvent, 1> eventsAfterReplacedEvent; > > The algorithm here is unnecessarily inefficient. We can find the event to replace by iterating the Deque without modifying the Deque. Then we can replace it in place. There is no need to remove all the events after it and then re-append them. This could be the function: > > auto it = queue.rbegin(); > auto end = queue.rend(); > // Must not merge with the first event in the deque, since it is already being dispatched. > if (it != end) > --end; > for (; it != end; ++it) { > auto type = it->type(); > if (type == incomingEvent.type()) { > LOG(MouseHandling, "UIProcess: replacing mouse event %s (queue size %zu)", webMouseEventTypeString(type), queue.size()); > *it = incomingEvent; > return; > } > if (type != WebEvent::MouseMove && type != WebEvent::MouseForceChanged) > break; > } > LOG(MouseHandling, "UIProcess: enqueueing mouse event %s (queue size %zu)", webMouseEventTypeString(incomingEvent.type()), queue.size()); > queue.append(incomingEvent); > > I left the logging in, although I think it makes the code a little confusing. Oh, I guess I should have read the discussion between you and Simon. You want to move the new event past the others.
Darin Adler
Comment 17 2018-07-30 09:35:42 PDT
Comment on attachment 346024 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=346024&action=review >>> Source/WebKit/UIProcess/WebPageProxy.cpp:1953 >>> + Vector<NativeWebMouseEvent, 1> eventsAfterReplacedEvent; >> >> The algorithm here is unnecessarily inefficient. We can find the event to replace by iterating the Deque without modifying the Deque. Then we can replace it in place. There is no need to remove all the events after it and then re-append them. This could be the function: >> >> auto it = queue.rbegin(); >> auto end = queue.rend(); >> // Must not merge with the first event in the deque, since it is already being dispatched. >> if (it != end) >> --end; >> for (; it != end; ++it) { >> auto type = it->type(); >> if (type == incomingEvent.type()) { >> LOG(MouseHandling, "UIProcess: replacing mouse event %s (queue size %zu)", webMouseEventTypeString(type), queue.size()); >> *it = incomingEvent; >> return; >> } >> if (type != WebEvent::MouseMove && type != WebEvent::MouseForceChanged) >> break; >> } >> LOG(MouseHandling, "UIProcess: enqueueing mouse event %s (queue size %zu)", webMouseEventTypeString(incomingEvent.type()), queue.size()); >> queue.append(incomingEvent); >> >> I left the logging in, although I think it makes the code a little confusing. > > Oh, I guess I should have read the discussion between you and Simon. You want to move the new event past the others. Here’s a version that preserves that behavior, but does not use a vector: auto it = queue.rbegin(); auto end = queue.rend(); // Must not merge with the first event in the deque, since it is already being dispatched. if (it != end) --end; bool removedEvent = false; for (; it != end; ++it) { auto type = it->type(); if (type == incomingEvent.type()) { queue.remove(it); break; } if (type != WebEvent::MouseMove && type != WebEvent::MouseForceChanged) break; } LOG(MouseHandling, "UIProcess: %s mouse event %s (queue size %zu)", removedEvent ? "replacing" : "enqueueing", webMouseEventTypeString(incomingEvent.type()), queue.size()); queue.append(incomingEvent); While it’s true that Deque::remove is not as efficient as Deque::removeLast, it is more efficient than moving all the elements out into a vector and then re-appending them.
Wenson Hsieh
Comment 18 2018-07-30 09:40:38 PDT
(In reply to Darin Adler from comment #17) > Comment on attachment 346024 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=346024&action=review > > >>> Source/WebKit/UIProcess/WebPageProxy.cpp:1953 > >>> + Vector<NativeWebMouseEvent, 1> eventsAfterReplacedEvent; > >> > >> The algorithm here is unnecessarily inefficient. We can find the event to replace by iterating the Deque without modifying the Deque. Then we can replace it in place. There is no need to remove all the events after it and then re-append them. This could be the function: > >> > >> auto it = queue.rbegin(); > >> auto end = queue.rend(); > >> // Must not merge with the first event in the deque, since it is already being dispatched. > >> if (it != end) > >> --end; > >> for (; it != end; ++it) { > >> auto type = it->type(); > >> if (type == incomingEvent.type()) { > >> LOG(MouseHandling, "UIProcess: replacing mouse event %s (queue size %zu)", webMouseEventTypeString(type), queue.size()); > >> *it = incomingEvent; > >> return; > >> } > >> if (type != WebEvent::MouseMove && type != WebEvent::MouseForceChanged) > >> break; > >> } > >> LOG(MouseHandling, "UIProcess: enqueueing mouse event %s (queue size %zu)", webMouseEventTypeString(incomingEvent.type()), queue.size()); > >> queue.append(incomingEvent); > >> > >> I left the logging in, although I think it makes the code a little confusing. > > > > Oh, I guess I should have read the discussion between you and Simon. You want to move the new event past the others. > > Here’s a version that preserves that behavior, but does not use a vector: > > auto it = queue.rbegin(); > auto end = queue.rend(); > // Must not merge with the first event in the deque, since it is already > being dispatched. > if (it != end) > --end; > bool removedEvent = false; > for (; it != end; ++it) { > auto type = it->type(); > if (type == incomingEvent.type()) { > queue.remove(it); > break; > } > if (type != WebEvent::MouseMove && type != > WebEvent::MouseForceChanged) > break; > } > LOG(MouseHandling, "UIProcess: %s mouse event %s (queue size %zu)", > removedEvent ? "replacing" : "enqueueing", > webMouseEventTypeString(incomingEvent.type()), queue.size()); > queue.append(incomingEvent); > > While it’s true that Deque::remove is not as efficient as Deque::removeLast, > it is more efficient than moving all the elements out into a vector and then > re-appending them. Good point! I'll change this to `remove()` instead of using a separate Vector<NativeWebMouseEvent>.
Darin Adler
Comment 19 2018-07-30 09:40:42 PDT
I think it would be even clearer to move the entire "remove old event" into a separate function that just returns a boolean: static bool removeOldRedundantEvent(Deque<NativeWebMouseEvent>& queue, WebEvent::Type incomingEventType) { if (incomingEventType != WebEvent::MouseMove && incomingEventType != WebEvent::MouseForceChanged) return false; auto it = queue.rbegin(); auto end = queue.rend(); // Must not remove the first event in the deque, since it is already being dispatched. if (it != end) --end; for (; it != end; ++it) { auto type = it->type(); if (type == incomingEventType) { queue.remove(it); return true; } if (type != WebEvent::MouseMove && type != WebEvent::MouseForceChanged) break; } return false; } bool removedEvent = removeOldRedundantEvent(queue, incomingEvent.type()); LOG(MouseHandling, "UIProcess: %s mouse event %s (queue size %zu)", removedEvent ? "replacing" : "enqueueing", webMouseEventTypeString(incomingEvent.type()), queue.size()); queue.append(incomingEvent);
Wenson Hsieh
Comment 20 2018-07-30 10:27:30 PDT
(In reply to Darin Adler from comment #19) > I think it would be even clearer to move the entire "remove old event" into > a separate function that just returns a boolean: > > static bool removeOldRedundantEvent(Deque<NativeWebMouseEvent>& queue, > WebEvent::Type incomingEventType) > { > if (incomingEventType != WebEvent::MouseMove && incomingEventType != > WebEvent::MouseForceChanged) > return false; > > auto it = queue.rbegin(); > auto end = queue.rend(); > > // Must not remove the first event in the deque, since it is already > being dispatched. > if (it != end) > --end; > > for (; it != end; ++it) { > auto type = it->type(); > if (type == incomingEventType) { > queue.remove(it); Note: currently, WTF's Deque just has remove(iterator&), not remove(reverse_iterator&). > return true; > } > if (type != WebEvent::MouseMove && type != > WebEvent::MouseForceChanged) > break; > } > return false; > } > > bool removedEvent = removeOldRedundantEvent(queue, incomingEvent.type()); > LOG(MouseHandling, "UIProcess: %s mouse event %s (queue size %zu)", > removedEvent ? "replacing" : "enqueueing", > webMouseEventTypeString(incomingEvent.type()), queue.size()); > queue.append(incomingEvent);
Wenson Hsieh
Comment 21 2018-07-30 12:50:16 PDT
Created attachment 346088 [details] Address review feedback.
Wenson Hsieh
Comment 22 2018-07-30 13:17:06 PDT
Created attachment 346091 [details] Address review feedback (v2)
WebKit Commit Bot
Comment 23 2018-07-30 14:47:37 PDT
Comment on attachment 346091 [details] Address review feedback (v2) Clearing flags on attachment: 346091 Committed r234384: <https://trac.webkit.org/changeset/234384>
Darin Adler
Comment 24 2018-07-30 19:12:26 PDT
Later, someone should add Deque::remove(reverse_iterator&) and clean this up.
Darin Adler
Comment 25 2018-07-30 19:24:49 PDT
(In reply to Darin Adler from comment #24) > Later, someone should add Deque::remove(reverse_iterator&) and clean this up. Maybe not. std::deque works exactly the same way, so perhaps we should just leave it like this.
Note You need to log in before you can comment on or make changes to this bug.