Forgot to post this on saturday - I spent a few hours trying to learn the humble primitives. Very nice! This is a spotify-ish ui. I did run into a couple questions along the way though: • First, performance. After around 200 elements paints were really costly. The main structure of the page is a stack with the top/bottom/list items, and the list is a column with two spacers at the top and bottom. I guess there isn't special handling for "offscreen-ness" in columns? I'm not sure. • Second, paint artifacts. You can see at the end of the video when I'm scrolling slowly that theres a "shimmer" at the top and bottom of the blurred areas. I'm not sure what's causing that? Seems to be related to a partially rendered text line. • Third (unfilmed), the top and bottom bars don’t capture the mouse. I see the hover effect when the mouse is on those bars. How can I turn those into a mouse sink?
Nevermind my assumption on #1, I looked at the virtualized rendering example again and I get the same behavior modifying that code - really long lists take a long time to render paint, even if only a small subset are actually drawn.
Do you have some repo for that?
Super nice stuff
I’d be eager to play around and investigate those performance issues
It's just a clone of the humble repo with an extra example. I'd be happy to publish it if you wanna mess around with it. I didn't (still don't) plan to take this shell to completion, so it's quite messy code 😂
Yeah, if you can publish, that’d be great. I’m also just messing around with Humble lastly so I’m just curious to check out what you’ve done :)
Don’t really care if it’s messy, just hacking around checking what works and what doesn’t
Gimme ~2h
1. Performance is what I’m working right now. There’s some virtualization (stuff not in viewport is not drawn, but still measured & laid out), can always have more. And there’s a terrible O² behavior in measure that I’m working on fixing 2. Blur like this requires some tricky compositing. I plan to explore that eventually but haven’t yet. There’s Blur demo in Humble repo, it has same problem 3.
Not sure about 3, you want it to prevent hover from happening on stuff underneath?
Yeah, basically in a stack how do I get the higher element to sink mouse events
I’ve had something like this somewhere recently, I’ll look into it again
Any performance bottlenecks besides algorithmic?
Lots, but so far what I see if I cache layout properly (even go from O(n²) to O(n)) most times collapse to under a ms. Should be fine
But I do see stuff like map lookups, dynamic bindings and arithmetic ops on flame graph, which is a bit worrying. Hope they don’t add up to too much
Using dunamic bindings is very wasteful
There are some tricks you can pull of map lookups' cost becomes noticeable
I can look at the graphs if you'd like
Can you run Humble UI demo app? It’s just clone & ./script/repl.py Then select any demo, scroll left panel all the way down and click Profile button. After about ~10 seconds it should generate a flame graph and print path to it in console
Oh, that’s how that’s supposed to work lol. That button crashes the app for me on Linux 🤣
Check if Linux instructions here help https://github.com/clojure-goes-fast/clj-async-profiler
It's been a while since I profiled it, I'll take a look later today
Lots of overhead from binding
you'll pay a lot less for it if you hand craft some macro which ends up emitting a call to RT.mapUniqueKeys(Object... init)
That's just two constructor calls which wrap the array and voila
Yeah, binding I plan to get rid of. I recently realized it’s all on the same thread, so can even do simple with-redefs
Also planning to extract scale to a separate top-level var, since 90% of use of ctx are for it, getting it through map lookup every time might not be the best idea
(defn evil-mapunique [& xs]
(clojure.lang.RT/mapUniqueKeys (.array ^clojure.lang.ArraySeq xs)))
(defmacro evil-binding [bs & body]
(assert (vector? bs))
(assert (even? (count bs)))
(assert (apply distinct? (take-nth 2 bs)))
`(let []
(push-thread-bindings (evil-mapunique ~@(mapcat (fn [[v e]] [`(var ~v) e]) (partition 2 bs))))
(try
~@body
(finally
(pop-thread-bindings)))))Nice!
btw, profiling borked on linux because of
# "-Dclj-async-profiler.output-dir=/ws/humbleui",
Right! I was wondering where did I put it but couldn’t find it right away
Wonder if can be made relative
Relative or under /tmp
Also, checked with evil binding and eliminated all calls related to creating the hash map and iterating over the keys
/tmp is the default, but kinda hard to find
push-thread-bindings still has to merge maps though, so probably still overhead
/tmp/ws?
I don't see the merge
I think it's a chained env
oh
I didn't get increased throughput, trying to figure out why
Rough estimate of binding's overhead:
(def ^:dynamic *a*)
(def ^:dynamic *b*)
(def ^:dynamic *c*)
(defn foo1 [] (binding [*a* 1] 1))
(defn foo1' [] (evil-binding [*a* 1] 1))
(cc/quick-bench (foo1)) ; 204.706525 ns
(cc/quick-bench (foo1')) ; 84.057809 ns
(defn foo2 [] (binding [*a* 1 *b* 2] 1))
(defn foo2' [] (evil-binding [*a* 1 *b* 2] 1))
(cc/quick-bench (foo2)) ; 371.001624 ns
(cc/quick-bench (foo2')) ; 178.677062 ns
(defn foo3 [] (binding [*a* 1 *b* 2 *c* 3] 1))
(defn foo3' [] (evil-binding [*a* 1 *b* 2 *c* 3] 1))
(cc/quick-bench (foo3)) ; 531.225786 ns
(cc/quick-bench (foo3')) ; 242.553081 ns
Thanks