-
-
Notifications
You must be signed in to change notification settings - Fork 53
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
Possible O(n^2) instead of O(n) cost ? #108
Comments
Something like this would do it: Will try some ideas. |
Thanks for the report, I reproduced the slowness locally. By my first measures, surprisingly, it does not seem that |
Actually, turns out I got distracted by another, less important, perf issue, but yes, |
Ok, I've worked through a lot of performance bottlenecks for this issue and related issues in Laminar and Airstream, and I have a solution. I was using js.Array's I have now tentatively switched to using js.Map for I also noticed that the Together these two measures have pretty much solved this performance issue. In Firefox and fastOptJS, time to render after entering "1" or "2" went down from 2-4 seconds to <50ms on my machine when rendering 2000 items. With 10000 items rendering the "1" filter takes ~850ms, with 20000 items it takes ~1500ms. But even the new logic gets exponentially slower as you go higher, e.g. 50000 items takes ~20 seconds to filter. However, that is not due to Speaking of removing nodes... I did all of these benchmarks above by putting your list in a div with Aside from this trick, seems that we're just hitting the browser limitations when it comes to removeNode performance. If you need to render 50000 elements, I suggest reading about what makes for more expensive repaint / reflow / etc. and how to work around that. OTOH I don't know much about that. You can also virtualize your list view, only giving Laminar a subset of rows to render, and changing that smaller subset as the user scrolls, that's a pretty common strategy. Downside is that ctrl+f does not work in such UIs, but it looks like you're implementing your own Ctrl+F UI or something similar anyway. I asked in scala.js discord what my performance expectations should be regarding js.Array, let's see what they say. It might not be fixable in js.Array because it's supposed to follow Scala I'll publish the code I have in a few days for you to check it out, it's a terrible (but working) mess right now. |
Thank you for such a quick and thorough reply. Let me start with saying I don't want to quite show 50.000 items :-) It's more of a grid which fits a few hundred items on a screen, with outliers to a few thousand (some users prefer this). Sub 1-second for 10.000 visible items as you describe would be an excellent result! I tried some solutions, basically indexing the This made things a lot better, after which the bottleneck became the I didn't think of falling back to plain javascript arrays, makes sense that they are highly optimized, and is good for the output javascript code size (compared to dragging in more Scala collections). One other thing might be though, that once you're at such large lists, trying to make small adjustments by reordering, inserting, and removing, that you might as well just recreate the whole thing and replace it in a single step (where applicable with regard to component state). In fact I tried this for my component and the only issue there is the ~1 second delay when going from showing 0 to showing 2000 items. I think your fixes will actually help this case too though. Will continue my component knowing this will be worked around / fixed, thank you and if you need me to test anything please let me know. |
Ha, we followed very similar trails diagnosing this, it seems :) It's rare that I get to use the profiler, it was nice to hunt this down. FTR, with my changes, repainting / reflowing will be the only bottleneck, so I think small adjustments (adding / removing a small number of elements) will actually work much faster than big adjustments and full re-renders, since the slowdown seems to be proportional to the affected element count and the repaint area, more or less. |
@Dennis4b Please check that Laminar 0.14.1 and Airstream 0.14.1 solve the issue for you (without creating other issues...). To keep it (kinda) binary compatible this release only has ~90% of the performance I mentioned, but it's still plenty fast. I'll add a few more optimizations to 0.15.0. |
@Dennis4b FYI it appears that the |
I'll keep this issue open for visibility until we settle this completely. |
Wow you're fast! Will be a great addition to Laminar. Couple of thoughts: In my particular use-case (live filtering), I found that the construction times of these particular items to be shown/hidden also plays a role (there's a lot of information crammed into a small item on a big dashboard). For this use-case ended up hiding/showing the items via a I will try to test it soon but I'm not using Finally, I can't really judge the impact of the |
Welp, yes, then 0.14.1 won't work for you properly. I just published Airstream 0.14.2 which should solve the issue, try it out whenever you're ready. BTW ScalaJS DOM 2.0.0 is actually a very easy upgrade, unless you have other dependencies that require the old version. |
The ScalajS DOM 2.0.0 requires a new scalajs-react lib which also requires some modifications, but I think I have it mostly working again. Using Laminar 0.14.1 with Airstream 0.14.2 the testcase from the original report now flies along with 5000 elements! So I would say your changes are a great success. When comparing hiding items versus removing/adding items in my use-case, due to the cost of creating items the showing/hiding "feels" slightly faster during live text filtering. Another function is changing the sort-order of the elements, which basically reorganizes all thousands of items inside the same container. I didn't test this beforehand to have a reference, but at the moment this code runs as fast as the React version it replaces, which when used with a All in all very happy! |
Closing this since everything seems ok with 0.14.2 |
Consider the following example:
(As test input, just enter for example "2")
With
itemCount = 1000
, it takes well under a second to reactWith
itemCount = 2000
, it takes several seconds to reactWith
itemCount = 3000
, it takes until the browser warns about stopping the script because things are taking so longI believe this should be linear degradation in
n
. Could there be a list lookup (instead of map lookup), or nested iteration, or similar, causing the execution time to explode asn
grows?(yes, while unusual, I have a component like this in production, currently with React where this works ok-ish, but creating the next version in Laminar and running into this problem)
Happy to test any suggestions!
The text was updated successfully, but these errors were encountered: