-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A new list diffing algorithm #1274
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1274 +/- ##
==========================================
- Coverage 91.88% 91.87% -0.02%
==========================================
Files 126 126
Lines 4548 4541 -7
Branches 1478 1478
==========================================
- Hits 4179 4172 -7
Misses 153 153
Partials 216 216
Continue to review full report at Codecov.
|
I borrowed an algorithm from Google's observe-js polyfill and implemented it here https://github.com/chip-js/differences-js/blob/master/src/diff.js#L199. (The original is too enmeshed into their other code and had to be extracted, so my version is the easier one to try and use) I really don't know what performance characteristics are being targeted so this may not be helpful at all, but I thought I ought to share in case it was. |
If it also handles both carousel directions .
Then it should handle the common cases. Having some tests to ensure the resulting DOM operations taken match exceptions would help in preventing a regression in the future. ivi, domvm, dio, etc, do this to some reasonable extent since some DOM ops can impact the correctness of a ui's end state with regards to input and other stateful elements beyond the supposed html representation. |
Thanks @jacwright. The observe-js approach looks like it probably has significantly worse performance in cases where there are many edits; it has a loop inside a loop in @thysultan yep, it handles those cases as described. You're right about regressions. Not sure how to test how many move operations it performs in a given case without adding code specifically for testing, but perhaps we need a more robust approach to benchmarking generally. Will freely admit that I generally skip that part, because running decent benchmarks takes forever. Have tidied everything up here and double checked the benchmarks, so I'll go ahead and merge this. |
Perhaps one way to test performance regressions is to add a wrapper to the JSDOM api which records DOM operations. Then the test can assert n DOM operations were performed. Also, I thought I covered all of the cases in the PR. Apologies for what I missed. It does look like you have something that works well though. |
No apologies necessary! It was a real corner case — I only spotted it because one of the 'random permute' cases happened to fail in CI; to reproduce it locally I had to increase the count from 100 to 1000. And one of the reasons I'm keen on extracting stuff like this out into shared helpers is that it's much easier to understand and change the code when it's written in a JavaScript file, rather than in a giant string with interpolate variable names etc — #588 stood open so long because I didn't have the stomach to wade into that unreadable mess, but you did! |
1.58.0 introduced a new list diffing algorithm, intended to solve the performance problems in #588. The original PR (#1249) was incredibly fast, but unfortunately had incorrect behaviour in some cases (where sequences of blocks were moving together). I modified the algorithm, thinking I was fixing the bug, but in fact I was also undoing the reason it was so fast, and everything actually became slower than it was before.
Oops.
I first tried to implement the classic list diffing algorithm described in this Medium post but... it's too complicated for my liberal arts major brain. I had a hunch that there might be a simpler approach with the same performance characteristics, that would avoid ping-ponging from the front to the back of the list. And here it is, in pseudo-code:
It's likely that this isn't novel at all, and it's possible that it has negative performance characteristics in circumstances I haven't considered. But it seems to work pretty well.
The secret sauce is
deltas
. When changingABCDE
toEABCD
and working backwards (which is slightly more convenient), there are two possibilities......one of which is obviously better — the second is one move, the first is four moves. But if you codify the rule that on encountering different keys you should always move the old key rather than the new key, then going from
ABCDE
toBCDEA
would take four moves instead of one.deltas
allows us to make the right decision in both cases. In the first example, E would have to move four spaces to get to its new home, whereas D would only have to move one. In other words, moving the old key (E) is 'worth' four moves. In the second example, E would only have to move one space, whereas A would have to move four — so we move the new key (A), not the old key (E).In the row swapping case that motivated all this work (the js-framework-benchmark) test, this algorithm is as fast as #1249, but also works for those tricky edge cases.
Would be grateful if anyone out there who is more knowledgable about these sorts of things can sanity check me though...
TODO