Skip to content
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

split into 2 tables "keyed" and "non-keyed" ? #104

Closed
leeoniya opened this issue Dec 21, 2016 · 22 comments
Closed

split into 2 tables "keyed" and "non-keyed" ? #104

leeoniya opened this issue Dec 21, 2016 · 22 comments

Comments

@leeoniya
Copy link
Contributor

leeoniya commented Dec 21, 2016

Looking at the table [which is getting way too wide IMO], the perf numbers are very difficult to compare because many impls are keyed and many are unkeyed. these are really 2 completely different perf profiles.

if you prefer to have a single huge table at the very least, it would make sense to order/group them by keyed vs non-keyed. a trivial way to see this is to assume everything < 130ms for "replace all rows" is unkeyed and > 130ms is keyed.

i think 2 tables will make sense and would provide much more meaningful comparisons (and also not span a 2 x 1080p screens :)

the vanillajs-keyed impl would need to be fixed for this as well: see #103

@krausest
Copy link
Owner

@leeoniya
I think that's a very good idea, I updated the results table in 9a548fc. I tried to add some explanation for keyed / non-keyed. Feel free to comment if you had a better one ;-)

I expected the non-keyed impl of vanilla-js to perform faster for remove than the keyed impl, but the reported duration didn't show that. It turned out that if I revert the icon resizing transition the results are as expected a bit faster for non-keyed. I haven't reverted the css in the repo yet.

There are a few implementations that behave like keyed for the remove operation that run very fast for replace all rows. I'll have to look into that the next few days...

@krausest
Copy link
Owner

@leeoniya
ractive is really interesting. For Remove it acts like the other keyed frameworks and the transition stops. If I add the following in the run method:

        let elem = document.querySelector("tr");
        if (elem) {
            elem.style.backgroundColor="#f00";
        }          

It behaves like non-keyed for repeated create 1000 row (the red row stays visible).

@leeoniya
Copy link
Contributor Author

leeoniya commented Jan 11, 2017

thanks!

i'll take a look at Ractive tonight. i can help you debug to ensure all the impls are doing the proper dom ops. there are several keyed impls < 140.83 for "replace all rows"...which is highly suspect.

the keyed vs non-keyed explanation looks good.

BTW, is there somewhere where all the impls are compiled? It's a huge PITA building everything from scratch.

@trotyl
Copy link
Contributor

trotyl commented Jan 12, 2017

@krausest
I think the framework/library that supports both keyed and no-keyed mechanisms should be list in both tables, since the user could choose which implementation to use according to their needs.

@leeoniya
Copy link
Contributor Author

leeoniya commented Jan 12, 2017

ok, I tested and as expected these are non-keyed (do not perform 1000 x removeChild or replaceChild calls):

  • simulacra v1.5.5
  • ractive-edge
  • ractive v0.8.5

i could not get the other two to either build or run, but there's zero chance of them actually being keyed given their low timings:

  • binding.scala v10.0.1
  • polymer v1.7.0

the fact that you can trick ractive into a "keyed" mode is interesting but i could not find any official keyed mode in the ractive docs and if it takes some kind of hack to get it to work this way it's probably not well suited for the task. ractive does behave as keyed for single removal, but not for mass clearing. i assume that the difference has to do with the actual click event serving as a hint for which dom element needs to be removed, whereas there's no such hint when you clear/replace the full dataset programmatically.

@trotyl lib/implementation authors are free (and encouraged) to submit both keyed and non-keyed versions if both are supported.

@trotyl
Copy link
Contributor

trotyl commented Jan 12, 2017

@leeoniya I have a question that is index-keyed being regarded as keyed or non-keyed? In some frameworks, an explicit statement is required for tracking them by index, letting it looks like keyed in syntax, but the key is not provided by data at all. And in some other frameworks, it implicitly uses the same mechanism for tracking data by index and do not require extra statement.

Should there be some clarification about the terminologies like keyed or non-keyed so that others could contribute more correctly?

@leeoniya
Copy link
Contributor Author

leeoniya commented Jan 12, 2017

@trotyl the distinction is simple: keyed implementations must not recycle/reuse dom nodes for differing keys (or object identities). how this requirement is satisfied by each framework and implementation is not prescribed. but generally this means that all keyed implementations will have to physically remove and insert new dom nodes for "replace all rows". non-keyed implementations may simply update the existing dom nodes in-place to match the new data.

@krausest
Copy link
Owner

@leeoniya @trotyl
I'd suggest adding a "litmus test" to find out which group a framework belongs to. As ractive shows, checking the remove operation alone is not sufficient. I suggest the following addition for each benchmark implementation.
Add code that changes the first row's color in "run":

            let elem = document.querySelector("tr");
            if (elem) {
                let hex = Math.floor(Math.random() * 0xFFFFFF);
                elem.style.backgroundColor = "#" + ("000000" + hex.toString(16)).substr(-6);            
            }                      

If the first row has some color after the run operation, the benchmark belongs to the non-keyed group.

Add code that changes the row's color that should deleted in "remove row":

                while (target && target.tagName!='TR') target = target.parentElement;
                if (target) {
                    let hex = Math.floor(Math.random() * 0xFFFFFF);
                    target.style.backgroundColor = "#" + ("000000" + hex.toString(16)).substr(-6);            
                }                                  

If the row where delete is click is colored, the benchmark belongs to the non-keyed group.
(If one of both tests is true it's considered non-keyed)

This also works nicely for react when using index as the key. This react impl would belong to the non-keyed group, which is right in my opinion.

I checked the impact of the two operations. The "-2" version has the code added, the other not. Seems like it has no impact on the replace all or remove row benchmark and all frameworks would have the same disadvantage.
bildschirmfoto 2017-01-12 um 22 42 12
What do you think?

@leeoniya
Copy link
Contributor Author

leeoniya commented Jan 12, 2017

👎 from me.

I don't think it's necessary to introduce convoluted hacks try to force frameworks to behave a specific way. If there is no keyed impl available or possible, then that's just a fact of life.

This one's more controversial, but I also don't think it's necessary to provide a visual way for authors to test whether their impl is keyed or non-keyed. The guideline is pretty simple (don't reuse/recycle dom nodes across different keys or object idents) and if they don't know which impl they are submitting (hopefully they do!), then we can classify it for them using some very basic DOM instrumentation. This means you can also remove the css transition on the X and manual verification on your end.

You can classify any framework automatically at runtime. Simply instrument insertBefore, appendChild and replaceChild, then execute "replace all rows" and look for some minimum threshold of op counts. You can do this as part of the warmup for each impl on some small dataset, like 10 rows.

if using DOMInstr [1]...

// render 10 rows...

var instr = new DOMInstr(true);

instr.start();

// run "replace all rows"

// account for async impls
setTimeout(function() {
  var counts = instr.end();

  if (counts.insertBefore + counts.appendChild + counts.replaceChild < 10) {
    // non-keyed
  }
  else {
    // keyed
  }
}, 200);

[1] https://github.com/leeoniya/domvm/blob/2.x-dev/test/dominstr.js

@krausest
Copy link
Owner

@leeoniya
What do you mean by "I don't think it's necessary to introduce convoluted hacks try to force frameworks to behave a specific way. If there is no keyed impl available or possible, then that's just a fact of life."? What do you consider a convoluted hack?

I'll look into automatic classification from the webdriver test driver. Do you think DOMInstr detects changes for shadow dom (polymer caused a lot of pain for webdriver so I'm alarmed). What advantage would DOMInstr have against MutationObserver?

@leeoniya
Copy link
Contributor Author

What do you mean by...

it requires modifying the current impls and complicating future impls for the sake of classification, which can easily be done without introducing anything special.

What advantage would DOMInstr have against MutationObserver?

most likely nothing, mutationobserver is probably better if available. dominstr was made to be compatible with unit testing for ie9+

@krausest
Copy link
Owner

Understood - and yes of course. I'll look into it.

The advantage of coloring the dom is that it illustrates the effect nicely also for people who aren't in the depths of keyed vs. non-keyed. I wouldn't have considered that sufficiently when choosing a framework and it might turn out as a big pain when working with external libs or introducing transitions.

@leeoniya
Copy link
Contributor Author

i agree that keyed vs non-keyed is not readily obvious and there should be a separate demo repo which visually illustrates the difference and the bugs that non-keyed can introduce.

i dont think it's necessary for every implementation to reproduce a subset of possible bugs.

@leeoniya
Copy link
Contributor Author

@krausest i have verified that ember-v2.10.0-beta.2 behaves as keyed. it looked out of place in the non-keyed table for "replace all rows"; it's not that slow :D

ember-v2.6.1 is almost certainly the same.

@krausest
Copy link
Owner

I've added webdriver-ts/nonKeyed.ts (invoked with npm run compile && node dist/nonKeyed.js in the webdriver-ts folder). It uses a MutationObserver and checks the kind of modification replace all rows causes and whether remove row removes the specific row that should be removed. The transition on the delete icon has been removed as it had a negative impact on the remove row benchmark.

It prints the following output:

angular-v1.5.8 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
angular-v2.2.1 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
aurelia-v1.0.7 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
binding.scala-v10.0.1 is non-keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
bobril-v4.49.2 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
cyclejs-dom-v14.1.0 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
dio-v3.0.5 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
domvm-v1.2.10 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
domvm-v2.0.0-beta is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
ember-v2.6.1 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
ember-v2.10.0-beta.2 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
elm-v0.18.0 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
inferno-v1.0.0-beta9 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
kivi-v1.0.0-rc2 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
knockout-v3.4.1 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
mithril-v0.2.5 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
mithril-v1.0.0-alpha is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
nx-v1.0.0-alpha.4.0.0 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
plastiq-v1.33.0 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
polymer-v1.7.0 is non-keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
preact-v6.4.0 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
svelte-v1.0.1 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
ractive-v0.8.5 is non-keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
ractive-edge is non-keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
react-lite-v0.15.27 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
react-v15.4.0 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
react-v15.4.0-mobX-v2.6.3 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
react-v15.4.0-redux-v3.6.0 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
riot-v2.6.7 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
riot-v3.0.7 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
simulacra-v1.5.5 is non-keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
tsers-v1.0.0 is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
vanillajs is non-keyed for 'run benchmark' and non-keyed for 'remove row benchmark'. It'll appear as non-keyed in the results
vanillajs-keyed is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
vidom-v0.5.3 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results
vue-v2.1.3 is keyed for 'run benchmark' and keyed for 'remove row benchmark'. It'll appear as keyed in the results

which is consistent with the findings so far. The results table has been updated.
@leeoniya @trotyl What do you think about that solution?

@leeoniya
Copy link
Contributor Author

👍 looks like we now have ourselves a useful benchmark, sir :)

@leeoniya
Copy link
Contributor Author

leeoniya commented Jan 14, 2017

@krausest as a separate but related note, it may be worth deciding on a structural nomenclature for keyed and non-keyed since implementations can exist in both flavors. i plan to submit a non-keyed domvm v2 impl, but dunno what to call it.

i propose appending -keyed and leaving the non-keyed without a suffix (for brevity). or a keyed- prefix may be easier to group by folder structure. although directory naming may require additional commits pre-merge of new implementations if authors get it wrong accidentally.

...or you can just leave it the way it is.

thoughts?

@krausest
Copy link
Owner

I'd introduce a naming convention only for frameworks that have both a keyed an a non-keyed submission. In that case I prefer the suffixes "-keyed" and "-non-keyed".

@leeoniya
Copy link
Contributor Author

leeoniya commented Jan 14, 2017

sounds good.

the results table may look cluttered if every header has suffixes. since they're now split up, the distinction doesnt need to be explicitly stated in each case. i guess you could strip the predefined suffixes during the table generation?

@krausest
Copy link
Owner

Absolutely.

@krausest
Copy link
Owner

I implemented the suffix removal, but somehow I don't like it when I looked at the results. It's clearer what implementation the results are for when the suffix is kept and it doesn't look too bad in the table (see angular 2).

@leeoniya
Copy link
Contributor Author

leeoniya commented Jan 15, 2017

cyclejs-dom v14.1.0-non-keyed is gonna take up 4-5 lines. probably a couple others too.

what about using the empty upper left cell in the header to say keyed or non-keyed and make it highly visible like blue background/white text. and different distinctive colors for non-keyed.

croppedimage

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants