You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -61,13 +61,48 @@ The app has three buttons, all of which give the same result: _The countries of
61
61
62
62
It's not all bliss, of course. We're still dealing with mutable(!) data. And we do need to sprinkle in the occasional `#js` tag, and do things like `js-interop/push!`, and try not to forget to use `into-array` to create JS arrays out of Clojure vectors/lists. It's price to pay for the performance gain. As small a price as I know about. The performance gain is quite significant in this case. The demo app lets you _feel_ the difference.
63
63
64
+
## Transform performance
65
+
66
+
The transformation happening in the demo app is rather small. We're only dealing with 250 countries. Maybe it holds for many real world scenarios as well, that we don't need to pay too much attention to this step, when the conversion are so performance consuming. Though, it could be that the data has tons of nodes that you need to update, and then the tradeoffs may look different. The demo app can be made to run its transform many times, as a way to simulate such a scenario. Here are the results on my machine running the transforms 1 000 and 10 000 times:
67
+
68
+
| Approach | ms 1K runs | ms 10K runs |
69
+
|------------|-----------:|------------:|
70
+
| clj data | 0 | 1 |
71
+
| js-interop | 100 | 1500 |
72
+
| as-jsi | 800 | 10000 |
73
+
74
+
*`clj-data` represents all solutions doing the transform using regular Clojure data transform functions, including the **cljs-bean**, and **Transit** examples.
75
+
*`as-jsi` is **applied-science/js-interop**, including **js-mode**.
76
+
*`js-interop` is Thomas Heller's “Embrace Interop” contribution, which skips using any library and thus enjoys no destructuring convenience.
77
+
78
+
The reason I added 10K was that I at least wanted a figure for the Clojure data case. It actually took 0 ms in some of the 10K runs I did, but anyway, on average 1 ms, shall we say? It's at least 3 orders of magnitude faster than raw JS interop for the demo app scenario.
79
+
80
+
Raw JS interop is almost a magnitude faster than accepting the overhead added by the **applied-science/js-interop** library.
81
+
82
+
But when pushed like this, the JS interop implementations have worse problems than being slower than working on Clojure data...
83
+
84
+
### All JS interop implementation stop working at 1K transforms
85
+
86
+
When running the transform many times, all the JS interop solutions break in that they produce bad data. It starts happening at around 1K transforms. At 10K it is consistent, the map does not get updated. This is how I run the transform many times:
87
+
88
+
```clojure
89
+
(defndo-x-times [x f & args]
90
+
(first (mapv (fn [_]
91
+
(apply f args))
92
+
(range x))))
93
+
94
+
(do-x-times1000 clj-data/->geo-json clj-input)
95
+
```
96
+
97
+
Why this would make the data corrupt is beyond me. But, yeah, it is mutable data we are dealing with...
98
+
64
99
## I love `js->clj` ❤️
65
100
66
101
Working with JavaScript data isn't exactly civilized business. It's mainly something that comes with the trade and performance considerations may in situations as those above close the door to using Clojure data for the job. In many other situations it doesn't matter. Even if you get JSON in and need JS data out. When the data structure is small taking the pains and risks involved with mutating data aren't worth the unnoticeable performance differences.
67
102
68
103
In other situations bringing in dependencies such as the **js-interop** library aren't worth it. Reading `(some-> event .-target .-value)` is very clear.
69
104
70
-
Anyway, when the performance hit is noticeable by the user, and transformation is involved, **js-interop** is your friend. It lets you keep much of your ergonomics while delivering performance to the user. Especially if **js-mode** leaves the experimental stage.
105
+
Anyway, when the performance hit is noticeable by the user, and transformation is involved, and you do not want to forsake conveniences such as superior destructuring, then **js-interop** is your friend. It lets you keep much of your ergonomics while delivering performance to the user. Especially if **js-mode** leaves the experimental stage.
71
106
72
107
## Update: cljs-bean
73
108
@@ -78,16 +113,14 @@ On X, [Martin Klepsch made me aware](https://twitter.com/martinklepsch/status/17
1. The “conversion” to Clojure data takes so little time that it can't be measured
88
-
2. The transform takes a bit more time (3-4 times more according to my unscientific measurements)
89
-
* This takes so little time for the data in this app that it largely doesn't matter anyway, I think
90
-
3. The conversion back to JS data takes significantly more time than with regular Clojure data and `clj->js`
123
+
2. The conversion back to JS data takes significantly more time than with regular Clojure data and `clj->js`
91
124
* But not nearly as much time as we gain from the conversion to Clojure data taking no time at all
92
125
93
126
This means that **beam-cljs** is not a viable option for the use case in this article/demo app. But for cases where you get JSON/JS in and do not need to produce JS data out it is bloody excellent! This find alone made it worth spending the time writing this article and app.
@@ -101,9 +134,9 @@ At [/r/clojure I learnt that you can too destructure string keys](https://www.re
We can see that David Nolen was very right about performance gains between `js->clj` and using `transit/read`. In addition to that we don't need to use JS to convert the response to JSON, so we save time there as well. This is quite much better, performance-wise than the naïve `js->clj` approach. And about the small price we pay, string key destructuring is very convenient! We are still locking the UI thread much longer than when using js-interop, though, so it is not really an option for the use case of the demo scenario.
0 commit comments