Fork me on GitHub
#reitit
<
2021-09-10
>
Ben Sless08:09:03

Weeeee, two performance PRs in ring merged 馃帀 https://github.com/ring-clojure/ring/pull/446 https://github.com/ring-clojure/ring-codec/pull/34 Hopefully they'll be able to make it into reitit soon

馃帀 6
delaguardo10:09:55

https://github.com/ring-clojure/ring-codec/pull/34/files#diff-e42d7d4760cc0ec1368c75ec5ec79429d64d6795c32717c6576540162ceffc9aR145-R147 I remember @U064X3EF3 talked once that devs shouldn鈥檛 use MapEntry directly. I鈥檓 curious - why it is used here? Does it give performance boost in comparison with returning a list of two items for example?

Ben Sless10:09:25

Yes. Faster than both a list and a vector. You wouldn't care about it unless you were doing over 10k qps

Ben Sless10:09:24

It's sometimes hard to know what can and can't be used from the core. I expect MapEntry is used in more than one library out there

delaguardo10:09:21

I found it - https://groups.google.com/g/clojure/c/FVcrbHJpCW4/m/Fh7NsX_Yb7sJ maybe it is already not relevant because it is from 2008.

Ben Sless10:09:54

"I make no promises about the continued existence of MapEntry"

delaguardo10:09:55

(defrecord Pair [k v])

(defn- split-key-value-pair [^String s]
  (let [i (.indexOf s #=(int \=))]
    (cond
      (pos? i)  (->Pair (.substring s 0 i) (.substring s (inc i)))
      (zero? i) (->Pair "" (.substring s (inc i)))
      :else     (->Pair s ""))))
I tried to use defrecord and here is my benchmarks:
# MapEntry
Evaluation count : 22697706 in 6 samples of 3782951 calls.
             Execution time mean : 25.380441 ns
    Execution time std-deviation : 6.288547 ns
   Execution time lower quantile : 19.789665 ns ( 2.5%)
   Execution time upper quantile : 32.343785 ns (97.5%)
                   Overhead used : 7.251431 ns

# Pair
Evaluation count : 19948320 in 6 samples of 3324720 calls.
             Execution time mean : 25.188445 ns
    Execution time std-deviation : 3.066662 ns
   Execution time lower quantile : 21.601103 ns ( 2.5%)
   Execution time upper quantile : 28.464897 ns (97.5%)
                   Overhead used : 7.251431 ns

Ben Sless11:09:31

I could define an ad-hoc pair type, compare Pair, MapEntry, [a b] and (list a b)

Ben Sless11:09:35

You also need to compare destructuring perf

delaguardo11:09:42

Yes, here is how I got the results:

(defn test-1 [^String s]
  (let [kv (split-key-value-pair-map-entry s)]
    [(key kv) (val kv)]))

(defn test-2 [^String s]
  (let [kv (split-key-value-pair-defrecord s)]
    [(:k kv) (:v kv)]))

(quick-bench
 (test-1 "foo=42"))

(quick-bench
 (test-2 "foo=42"))

Ben Sless12:09:01

Again, I expect no significant difference for map entry and record, you'll see those with list and vector Maybe you'll see a difference with map entry if you direct link

Ben Sless12:09:42

The main difference is that MapEntry exists in Clojure and can be destructured like a user will expect. We don't have tuple or nominal tuple types, MapEntry lets me fake one instead of defining a bespoke one. Maybe it's my bias but I see introducing a new type as having a higher "pass" threshold than introducing a new function.

delaguardo12:09:48

yeah, fair point

ikitommi15:09:46

Great that those got in, congrats!

Karol W贸jcik18:09:28

@UK0810AQ2 you are amazing man!

馃槄 2
Ben Sless18:09:57

Thank you all. I'll update a new round of results in stress-server tomorrow morning and will rerun when these get merged. Really curious to see how much can be gained

Ben Sless18:09:49

We will all reap the benefits 馃檪

Ben Sless08:09:02

Which makes me wonder, can reitit be migrated to deps.edn and tools.build?

ikitommi15:09:41

sure, malli could also be a multi-module project. I don't have time to migrate, but help most welcome :)

Ben Sless16:09:51

Can't wait to see how much it shaves off the wall

kirill.salykin09:09:02

Hi, I am trying to coerce data using the spec-tools, but it seems working not the way as I expected:

(require '[spec-tools.core :as st])

  (s/def ::double double?)
  (s/def ::x (s/keys :req-un [::double]))
  (st/coerce ::x {:double 100} nil)
My expectation that double will become 100.0, but it is not the case atm Are my expectations correct? Or what I am doing wrong? thanks!

ikitommi15:09:33

@kirill.salykin I would guess having a nil transformer means "do nothing". Also, not sure if JSON/string transformers in spec-tools have mapping from numbers to double, but should be easy to add, also in user side. Pretty sure that malli has those.

kirill.salykin15:09:17

So coercion works works from the json/string representation only? No way to coerce clojure into clojure?

ikitommi17:09:13

you can do any kind of x->edn->x transformations, transformers are both easy to compose and extend. There just isn't a edn->edn transformer pre-packaged, so you have to build one yourself. PR welcome. Here the list of current transformers: https://github.com/metosin/spec-tools/blob/master/src/spec_tools/core.cljc#L81-L176

kirill.salykin17:09:50

Actually it think strip-* might work for me

Tomas Brejla18:09:31

Hello, I'd like to ask whether the Dev workflow approach described at https://cljdoc.org/d/metosin/reitit-ring/0.5.0-SNAPSHOT/doc/advanced/dev-workflow also applies to reitit.ring/router . Basically I have a reitit ring router with some routes and I want the changes to routes to be refreshed automatically when I save/evaluate the changed definition. Currently I have to issue an integrant system reset which in turn stops and starts new instance of httpkit server with changed routes. This works fine, but I believe shouldn't be necessary. Any hints welcome 馃.