Fork me on GitHub
#off-topic
<
2024-04-01
>
chucklehead02:04:29

any recommendations for a dock/port replicator for an M2 MacBook? just need power, gigabit ethernet, single external monitor over HDMI or DisplayPort, and some USB-A ports for keyboard/mouse.

D06:04:20

I am using D-Link DUB-M810, and it worked flawlessly with MacBook Pro 13 (10 cores), including its power requirements. Then I switched to MacBook Pro 14 (10 cores), and I received a new definition of what "unstable" means: its power requirements exceeded the power provided by the hub. So, I had to use a separate power cord for the laptop itself. HTH.

1
D06:04:20

In my setup I am using the USB hub of my monitor to connect the external keyboard, mouse, camera and Jabra speaker, so only a single USB cable goes to the hub itself, and this significantly reduces the cable clutter on my desk.

chucklehead00:04:25

You totally saved me from myself. When I originally bought this monitor I got it specifically to be able to share the keyboard/mouse between my desktop and a Surface Pro through the monitor’s hub, but I haven’t had the Surface for a few years now and completely forgot I could do it.

1
Lidor Cohen12:04:08

Hi everyone 👋 Wasn't sure what's the appropriate channel to ask so I thought I'd start here 🙂 A bit of intro: My company built a sophisticated UI framework for the browser in cljs (we use reagent's rendering but not state). We are very happy with its interface and features but are now facing some performance issues. We are looking for some professional help. We're not sure how to start so it's hard to specify expertise, but we're looking for someone with experince with: • UI framework development \ • Complex low level abstractions (what's costly / how to analyze) • Anyone who think they can help get to our goal and are willing to take the job and work with us on this challenge 🙂 (if there's a better channel for this, please let me know)

p-himik12:04:19

At the very least you should mention the platform. Is it for the web? For desktops? For mobile? What is it built upon, if anything? Also, have you already tried plain profiling? Did it not produce any actionable results?

Lidor Cohen12:04:46

Sorry, It was blindingly obviouse to me 😅 I'll edit. And as for profiling: we tried but were unable to extract meaningful insights. We'll try to learn more, but ATM we are also starting looking for a guide...

p-himik13:04:16

A few things worth mentioning: • Dev performance is much worse than prod performance • Except for the most egregious cases, you should be profiling the prod version with pseudo-names turned on • Since it's built on top of React, there's React DevTools with its own component-level profiling. But it, of course, works only with a dev version

john13:04:00

reagent... are you using component local state? Are multiple components coordinating state between them?

john13:04:38

Migrating a reagent app to re-frame state management can often make a lot of bugs/performance issues disappear

john13:04:38

Component local state can have better performance, but only when the components are truly uncoordinated

p-himik13:04:40

Global state can have the same performance if you don't pass it around as a plain value but instead wrap it in a cursor/reaction.

john13:04:32

I'd argue that there are some particular things you can squeeze more perf out of using local state. Like the hover state of an element, where truly only those pixels and your user care about the hover state and nothing else in the app, then there's no point in entailing that value in the global state coordination system

john13:04:20

like, if there's thousands of these cells on a spreadsheet app, you don't need to store the hoverstate of every cell in the app-db

p-himik13:04:32

Well sure, my general reasoning for global state is the one inherited from re-frame. If it can be considered the state of your app, it makes sense to make it global, and that can be optimized "enough". Things like cursor position, hover status, animation state, etc. are IMO not a part of the application state. But even if you do store hover state in the global data storage, it shouldn't affect the overall performance unless you e.g. keep using ever changing integers instead of true.

john13:04:53

I like to think: if the data is point to point - only a single pair of produce/consumers - then it doesn't need to be in the app-db, it can just be a direct event signal

john13:04:00

through an atom

Lidor Cohen13:04:40

@U2FRKM4TW thanks for the tips • We face performance issues in prod • We did the profiling in prod we just don't know how to analyze it • It's hard to utilize the react dev tools because our framework kind of "generate" componnents so we're not looking for a problem with specific component to rewrite (we don't write components). @U050PJ2EU @U2FRKM4TW As for the reagent state conversation: We have our own reactive state-propogation system. We basically just invoke force-update for the relevant components and handle the rest ourselves (we're not even using r/atoms). We need a very specialized eye here, it's not your general react app optimization. A relevant insight might even be: don't use reagent but <some other rendering library>... Another relevant insight might be: use a different algorithm for... Or Use a different datastructure for...

p-himik13:04:28

The third point might potentially be problematic because Reagent does a lot of caching. If you keep recreating components, that cache becomes useless, even detrimental. If you don't use Reagent state propagation, then I'd say you should most definitely not use Reagent. Besides state propagation, the only thing it has is syntax for defining components, and that's superficial enough to not be a required argument when selecting a library.

john13:04:46

If you're looking for perf tricks I'd look into shadowgrove

john13:04:18

@U05224H0W is doing a lot of stuff at macro time, I believe

p-himik13:04:19

I wouldn't. :D Last time I used it, about a year ago or so, it was quite a struggle. No comprehensive docs plus there were no affordances for some approaches. Thomas confirmed as much when I asked about it. Maybe the things have changed since then, no clue.

john13:04:07

Well, you could use some of the perf ideas

p-himik13:04:07

But "performance tricks" can't be useful anyway if you don't know what makes the performance worse.

john13:04:09

Well, if it's not state management, but has to do with rendering, the tricks there might help

john13:04:03

It's precompiling to html as much as possible, I believe

p-himik13:04:35

It's like saying "if you suck at driving, you should ask people how to drive better". :) But why does somewhat suck at driving? What is exactly the problem? Maybe it's roundabouts, maybe it's reversing, maybe it's parallel parking. If it's any of those, asking for an advice from a rally driver won't help in any way.

p-himik13:04:25

I have a client who has other devs working on another product that has to communicate with what I work on. Up to a recent point, they had been experiencing incredible performance issues, whereas my thing that does exactly the same and more works perfectly. And throughout the months, the approach was "oh, I know - the X can be improved". So they toil away at improving X, nothing changes, they keep thinking. "Eugene, do you cache your data? We want to cache the data - it should make things faster" - nope, it wouldn't make things faster. Fortunately, they ended up finding (or stumbling upon?) a solution.

p-himik13:04:39

@U8RHR1V60 Can you share a profiler snapshot done in this way? 1. Build the prod app with pseudo names 2. Start the app, do some particular thing that is perceived as slow a few times 3. Start the JS profiler 4. Do the same thing a few times 5. Stop the profiler, download the snapshot

👀 1
Lidor Cohen15:04:44

@U2FRKM4TW, sure Do you specifically want me to do the slow act a few times without recording first? like a warm up?

Lidor Cohen15:04:28

I assume chrome (right?)

p-himik15:04:57

Yeah, I personally have very little experience with the Firefox profiler.

john15:04:06

Is the whole UI hanging for 2 to 3 seconds?

john15:04:18

Looks like it's hanging on that fetch call. Dunno why that'd hang your render loop though

john15:04:38

Are you downloading an LLM? lol

john15:04:53

2.5 second fetch is gratuitous

john15:04:29

You're not doing a synchronous xhr are you?

john15:04:36

Not sure if that's still possible on the main thread

john15:04:59

Naw I don't think fetch has that

Lidor Cohen15:04:26

There's a one sec (computations) delay before the fetch The fetch itself takes about 300ms And hang another ~2secs (of computations I guess)

p-himik16:04:43

Are you sure it's the production build? I see react-dom.development.js there in stacktraces. A lot of time seems to be taken by ui-framework.experimental.obj-val-v2.core/olookup- (or something like that, I was demunging mentally). You also seem to be relying on multimethods a lot, although that's hard to judge for me based on the profiler data alone. You're also relying on Specter - I would suggest avoiding it. It noticeably increases the bundle size and it can also make everything slower if you aren't careful with the way you create paths. Also, just the fact that you need it in the first place suggests that your data might be organized in a non-optimal way. There might be other insights in the data, but all that above should provide enough work. :)

p-himik16:04:53

Oh, and it's incredibly suspicious that a single click that visually only results in a few elements on screen does that much work. Are you sure that such a click doesn't also change all your data in some way?

john16:04:32

Is there some browser automation thing creating clicks?

p-himik16:04:49

You would see those clicks in the profiler data.

john16:04:35

Well something must be causing the reactions to rerun in a loop right?

p-himik16:04:49

Why do you think there's a loop?

john16:04:54

seems like some cache invalidation loop

p-himik16:04:36

I don't see loops, or caches. Just extensive data propagation through the same olookup-.

john16:04:02

Are you seeing lots of render calls?

p-himik16:04:43

I do. It doesn't mean there's a loop or a cache invalidation.

john16:04:34

It should only re render when there's a change in state

Lidor Cohen16:04:37

@U2FRKM4TW Thank you for the analysis. You're right I heavily rely on multimehtods: • I have one use case of multi-dispatch. • The rest is are cases of derive hierarchy + single dispatch. We use specter mostly to convert one dsl to a lower level dsl. We tried removing most of the usage of specter and it ended up a bit slower. And you're also right that most of the work is just data propogation thorugh lookup, imagine computations graph that depend on each other. In this case a click invoke a state change which causes computing some part of the page (which requires fetch) and render it.

john16:04:08

There could be a side effect in the render somewhere, updating the app db, further causing re renders

p-himik16:04:06

Hierarchies are slow. You shouldn't trust me on this and should test them yourself, but last time I checked, they were significantly slower than using multimethods without them. But it's been years and I can't guarantee I've done those tests correctly. I can guarantee, however, that they are most definitely slow if you keep changing the hierarchies themselves, like calling derive all the time. Just avoid converting between different DSLs. > computations graph that depend on each other. That sounds exactly like re-frame subscriptions with signals. :) Which is a graph of Reagent reactions with a single ratom at the root. And yet, re-frame isn't slow. For two reasons - Reagent caches reaction values and re-frame caches reactions themselves. (Although Reagent still has an issue with inconsistent values when your have dynamic rhombus-like dependencies, but I've never seen those in real code).

p-himik16:04:12

@U050PJ2EU Pretty sure those would become nested renders, not a series of renders all at the top level.

Lidor Cohen17:04:09

We don't have dynamic derives, just a static hierarchy for expressiveness. The DSLs are part of our system 😕 they allow us to work in a higher level of abstraction. We also cache reaction values, we even solved the inconsistency that exists in reagent. The difference in our system is that the computation of each value involves a lookup in an hierarchy of values (like generic methods in CLOS), which adds overhead that we are trying to optimize.

p-himik17:04:46

I'm not saying "avoid DSLs altogether", but by converting between them, you're introducing an extra step. Of course, that might not be a problem at all if amount of work that needs to be done in order to do that isn't that big.

p-himik17:04:33

Ah yeah, it was you who asked about CLOS. :) Back in that thread, I suggested caching the computation for each hierarchy or maybe even each object if that makes more sense. Have you tried that?

Lidor Cohen17:04:51

Yes, I redesigned the algorithm to maximize caching 🙂

john17:04:42

Do you have a setTimeout running in a tight loop during the duration of the fetch?

john17:04:09

Touching your caches, by chance?

Lidor Cohen18:04:40

No timeouts We are managing our caches so... Yes.

john18:04:54

Sounds like a bug. Maybe a performance bug, but still a bug. A click and a fetch shouldn't lock the browser up for a few seconds. An event triggering a layout change should only cause a few milliseconds of update computation. Is there anything else that you know is expensive happening?

p-himik18:04:23

Is the code open?

Lidor Cohen18:04:01

@U050PJ2EU that's what I'm trying to find out @U2FRKM4TW no 😕

p-himik18:04:43

Stumbled upon this, might make some things easier to see, especially the "Left Heavy" mode. https://www.speedscope.app/

john18:04:25

It just seems like during the duration of the click/fetch action, something is triggering the render/propagation loop many times a second. Maybe something is updating the status of the download to the user? Any debouncers or rate limiters banging on some app db in relation to the click/fetch event, that's leaking down into rerenders?

Lidor Cohen19:04:21

@U2FRKM4TW Thank you, I'll try it @U050PJ2EU there are no re-renders, only one render, our reactive engine is very precise, the computation takes time

john19:04:11

Yeah, I'm no inspector ranger

john19:04:05

But it looked like some layout function was being called many times in succession

john19:04:17

If you're doing some kind of thing like react's reconciler, you could try moving that big computation off the main thread. Then at least you wouldn't lock up the UI

john19:04:48

It's just not clear to me what computation is taking so much time, other than some possible render/state loop

Lidor Cohen21:04:29

Moving the computations off the main thread is an idea :thinking_face: I know flutter is doing everything async and I wonder if we need to go that way with our computation graph, or maybe just the layout computation...

p-himik21:04:35

Chances are, it won't help much. People have done plenty of experiments in the area, and I don't think there's any serious library out there that uses such an approach. The problem is that your data needs to be serialized and deserialized, which takes time, and not everything can be serialized.

p-himik21:04:03

I want to make that point a bit more clear. Judging by the profiler snapshot, something is very wrong with the code. Maybe it's some tiny bug with severe consequences, maybe it's a whole design, maybe it's just how it works in the realm of CLJS - I don't know. What I do know is that under no circumstances displaying a few fields (judging by the screenshots embedded in the profiler snapshot) should take more than a few tens of ms. I'm using re-frame to create UIs with tens of thousands of reactive components, each using at least one subscription - it all works very smooth. Occasionally I make mistakes (e.g. there was a bug where marking one row in a table would re-render all the cells in all the rows in a single column), but even those don't really degrade the performance to such an extent as is shown by the snapshot. So, shifting all that work to a different thread, even if JS had proper multithreading, wouldn't solve anything at all. If action X doesn't freeze the UI but still takes 2 s to complete, nothing prevents a user from doing also Y and Z and however many other things that would end up hanging the whole app anyway.

john21:04:40

Well, it would solve the UI freezing, presumably. But yeah, it's still a problem

p-himik21:04:13

A UI freeze is not a problem, it's a symptom.

Lidor Cohen06:04:28

@U2FRKM4TW , actually what happens in this case is very similar to rerendering all the rows in a table: An element (or a set of) is added to the screen which causes the layout to recalculate most of the screen (it depends on where in the hierarchy we added children basically, but wherever we did, the parent needs to recalculate its children and re-render all of them). Is there a technique (in react or else where) to mutate the population of a collection (i.e add/remove a child) without re-rendering the entire collection? In other words: with your table example, if you'd add/remove a row somewhere in the table will it not re-render the entire table?

Lidor Cohen06:04:49

By the way I agree with your analysis @U2FRKM4TW , my first choice is to make the computation as efficient as possible (without losing from our abstraction's flexibility, in our design principles flexibility is at the top). Given I did that (which I'm not sure, as you said there might be (and probably are) inefficiencies in our implementations, but I already redesigned the algorithm around caching so I'm not sure what the next steps are), but given that I did and that is the inherent complexity of the abstractions, another step might be distributing the computation to gain better performance. I'm no expert but I think graphics do that all the time (that's the whole idea of GPU to my understanding) because there aren't many doubts around the inherent complexity (or volume) of the calculations. Just to be clear: I'm not killing the doubts surrounding the efficiency of our implementation, I very much want experienced engineers to take a look from that perspective, and clear or culprit our implementation (without questioning too much the interface or the essnse of the abstraction. This is the abstraction we want to create. It is more our product than the apps that are built upon it), I'm just reminding that this is not an app performance analysis but a framework performance analysis, and there might be a need to consider that given the design requirements there's an inherent complexity that calls for a different approach to resources than is normally expected of a normal react application.

p-himik07:04:03

> with your table example, if you'd add/remove a row somewhere in the table will it not re-render the entire table? Of course it won't. It would be a disaster otherwise. When you add a row and make now other changes, the table component is called first, then the row component for the new row. And that's it. No other component is called - because they are already rendered. I just double-checked just in case with this code:

(ns clj-playground.core
  (:require [reagent.core :as r]
            [reagent.dom :as r-dom]
            [clojure.browser.dom :as dom]))

(defn row [data]
  (js/console.log 'rendering-row)
  (into [:tr] (map (fn [v] [:td v])) data))

(defn table [columns data]
  (js/console.log 'rendering-table)
  [:table
   [:thead (into [:tr] (map (fn [v] [:th v])) columns)]
   (into [:tbody] (map (fn [v] [row v])) data)])

(def columns ["a" "b" "c"])

(defn mk-row-data [idx]
  (mapv #(str % idx) columns))

(def n 100)
(def data (r/atom (mapv mk-row-data (range n))))

(defn app []
  [:div
   [:button {:on-click (fn [_]
                         (swap! data (fn [data]
                                       (conj data (mk-row-data (count data))))))}
    "Add row"]
   [table columns @data]])

(defn ^:export init []
  (r-dom/render [app] (dom/get-element "app")))
Surely enough, only rendering-table and rendering-row are printed once when you press the button.

p-himik08:04:17

When table is called when a new row datum is added, it returns a new Hiccup vector. Reagent converts the vector into an element, which requires converting every child into an element as well. So it gets to the first row. The row function is the same - the React component is cached. A React element is created then, but the data is the same, so React doesn't re-render it (thus not calling the row function). It gets repeated for every row till the added one. The same logic applies, but the data is new (and the whole element, for the matter), so React has to render it. It does so, that calls the Reagent impl that calls the row function and then converts the resulting Hiccup vector into new React elements.

Lidor Cohen09:04:01

Well, there's a bit of blundering here and I'll explain: First I modified a bit the test case:

(swap! data (fn [data]
              (into [(mk-row-data (count data))] data)))
and:
(swap! data (fn [data]
              (sp/setval (sp/before-index 25) (mk-row-data (count data)) data)))
In both cases react will re-render all the elements after the inserted element (this would happen also with remove) I think this case illustrates how react views the identities of elements in a collection. I think that by default it's a combo of component and order. if the same component in the same place recived the same props: do nothing. However when I inserted an item to the first place in the vector (basically shifted the entire list by one offset) react assumed [component, order] idetity didn't change so it must be different props so re-render. However if I'll add the following trick:
[:tbody
    (for [v data]
      ^{:key v} [row v])]
Basically nailing the identity of the element to some given value (:key), only one element, the relevant element, will render. Now back to our case: Layout recieves a modified vector of children and given some new params (props but not really) compute their components(! not elements...). Anyway you helped me realize we should probably handle this differently and not recompute the entire components tree on every change, but rather use keys and some way to pass the params that will tell react: "this is the same component and nothing changed"

p-himik09:04:55

Spot on about the key. > compute their components I've mentioned it before - this is definitely something that should be avoided.

p-himik09:04:20

The more defns, the better for React. :)

Lidor Cohen09:04:51

Your'e right, but our system "generates" elements dynamically from data structures kinda like (I think) reagent generate elements from hiccup. We probably can have some real gain here too... Either learning from reagent how to do it efficiently or use a more appropriate tool for the job. We used reagent because we came from react and we wanted to stay connected to the ecosystem. However this is no longer the strong case it used to be, at this point we might be better off moving to some super efficient rendering engine and mount react components at the leaves in case we need to.

p-himik09:04:47

> learning from reagent how to do it efficiently Skip this step. :) There's absolutely no reason for you to be using Reagent in the first place. There are other React wrappers that are much closer to React as compared to Reagent.

Lidor Cohen09:04:15

Do you have any specific recommendations?

p-himik09:04:03

Helix, UIx, raw React. Or something else, if you don't really care about event React.

Lidor Cohen09:04:35

I'm not confined I'd consider every library that can fit the task. One thing that I think is not an obviouse requirement is: generating elements dynamically without the need to create "static" components (or factories, if you will) to do so efficiently. I'm not very experienced in this area but I got the impression that Helix and UIx are based on macros and It's not obvious to me how to adapt them to our usage.

Lidor Cohen09:04:32

Solid also has some "compilation" step that prevent it from being obvious if and how we can use them

p-himik09:04:18

IIRC both of the libraries have escape hatches where you can create components dynamically.

Lidor Cohen09:04:54

Thanks 🙏, We'll do a survey of the solution space

p-himik09:04:38

But what exactly drives the need to create components dynamically? What do you do with them that can't be expressed as input data?

Lidor Cohen10:04:38

Hmmm that's a good question :thinking_face: I think the best answer would be that we have data that is processed until it becomes somethig that can be turned into a UI element (component)... It's kinda like hiccup but imagine you don't have components (or functions) barriers. Everything is data all the way down.

john13:04:22

I made a thing for that :)

john14:04:09

I call them "affects" or "data functions"

john14:04:16

And I made them to solve the front end UI expression problem

john14:04:48

Maybe should be called the composition problem, or concretion problem

Lidor Cohen14:04:17

@U050PJ2EU no sure if you're joking or not 😅 but in case you're not, can you share something of that? that sounds interesting

john14:04:42

No joke. For sure

john14:04:09

So here's the idea from two years ago: https://github.com/johnmn3/af.fect But more recently, I was talking about it in the architecture channel and came up with a new api: https://clojurians.slack.com/archives/C0904S2QJ/p1711520124934229?thread_ts=1711045846.649609&amp;cid=C0904S2QJ

john14:04:48

So as you can see, you can manipulate functions as data:

(daf add {:op +})

(def add-and-inc
  (-> add
      (update :out conj inc)))

(add-and-inc 2 2) ;=> 5

john14:04:09

I should have a CLJS version of the API out in a few days/weeks

john14:04:43

I have a prototype framework built on the old affect system here: https://github.com/johnmn3/comp.el

john14:04:00

and I'm working on porting it to the new more data-oriented api

john14:04:41

All of it is all prototype, spitball idea stuff, not ready for prod

p-himik14:04:06

:D It's very, very hard to read af in some code and not think something else.

😂 1
john14:04:28

the names are tentative

john14:04:53

I like the attitude though lol

john14:04:02

feels very gung ho

p-himik14:04:07

But I fail to see how that approach can possibly help with the problem of components being changed from under the Reagent's feet, resulting in complete re-renders.

john14:04:38

Yeah it's not for solving that problem directly

john14:04:04

For my purposes, it's about code growth and reuse

john14:04:43

But it shouldn't make that problem worse, afaict

Jeongsoo Lee15:04:09

Have you all heard the news Clojure's dropping s-expressions? Prepare for migrations trollface

clojure-spin 9
🎉 1
💯 1
Noah Bogart15:04:01

Listen, we tried Dylan and it was pretty cool but just not worth the effort.

trollface 2
Omer ZAK15:04:18

I have been told that Clojure (and all LISP-inspired languages, for that matter) is dropping defmacro due to Kentucky having passed a law banning macros in homoiconic languages and allowing it to persecute also people who use those macros in other states and countries. The reason for the ban is an obscure but extremely serious theological problem with the juxtaposition of homoiconicity and macros.

Nundrum16:04:45

Hmmm what would a heteroiconic language look like?

😂 2
Nundrum16:04:12

I take it back

😂 1
Omer ZAK16:04:27

Python is homoiconic-by-proxy, by virtue of the https://docs.python.org/3/library/ast.html module. However it does not have macros.

bzmariano19:04:00

Hi. http://clojure-goes-fast.com/blog is down since at least saturday. Maybe someone can mention the author.

oyakushev09:04:23

Damn SSL! Thank you for pinging me about this, fixed as of now.

👍 2