malli

https://github.com/metosin/malli :malli:
Ben Sless 2021-07-02T06:29:55.261700Z

@ikitommi could it be related to into transformer using satisfies? It's notoriously slow and GC intensive

ikitommi 2021-07-02T07:16:48.261900Z

it could, easy to see from flamegraphs.

rmcv 2021-07-02T07:18:45.263100Z

How do I provide sci options in malli decode? Trying to use java class with no luck.

(m/decode [:int {:decode/string '(fn [_] (.toEpochMilli (java.time.Instant/now)))}]

          "whatever"

          {:classes {'java.time.Instant java.time.Instant}}

          mt/string-transformer)

Ben Sless 2021-07-02T07:40:16.263700Z

I can already guess from my experience with recursive calls to satisfies? , you'll see 95% of CPU is wasted on it

ikitommi 2021-07-02T09:19:08.264100Z

no atm, but could be. Thing is that you can present things with malli that are impossible to present as specs, so it would not be complete like malli->JSON Schema , which is lossy. Still, I think 95% would work ok. There is malli-instrument library for doing the same with malli, if that’s ok for your use case.

ikitommi 2021-07-02T09:21:00.264400Z

looking at the code, it’s :malli.core/sci-options key in options, see https://github.com/metosin/malli/blob/master/src/malli/core.cljc#L1792-L1813

ikitommi 2021-07-02T09:41:41.265700Z

@ben.sless merged the loop unrolling perf PR, great for common cases and as it’s just clj, doesn’t make the cljs bundle size any bigger, big thanks!

;; 164ns -> 36ns
(let [valid? (m/validator [:and [:> 0] [:> 1] [:> 2] [:> 3] [:> 4]])]
  (cc/quick-bench (valid? 5)))

2πŸ‘3πŸŽ‰
ikitommi 2021-07-02T10:45:55.266600Z

merged the other perf PR too:

;; 150ns -> 39ns
(let [valid? (m/validator [:map [:a :any] [:b :any] [:c :any] [:d :any] [:e :any]])
      value {:a 1, :b 2, :c 3, :d 4, :e 5}]
  (cc/with-progress-reporting
    (cc/quick-bench (valid? value))))

13πŸŽ‰
Richard Hundt 2021-07-05T09:47:04.309300Z

@ikitommi I've created https://github.com/metosin/malli/issues/472 with a test case which demonstrates the issue.

Ben Sless 2021-07-05T11:01:07.309700Z

@richard.hundt I profiled you example, it looks like CPU mostly goes towards parsing the schema. If you wrap every def-ed schema with a schema constructor it seems to be way more efficient, would you mind trying?

Richard Hundt 2021-07-05T11:02:26.310100Z

D'you mean just wrapping it in a function which returns the structure? (I'm not sure what you mean by "schema constructor")

Ben Sless 2021-07-05T11:06:25.310400Z

like so;

(def A1
  (m/schema
   [:map
    {:encode/test {:leave encode} :decode/test decode}
    [:type [:= "A1"]]
    [:value float?]
    ]))

Richard Hundt 2021-07-05T11:06:44.310600Z

thanks, I'll try

Ben Sless 2021-07-05T11:06:44.310700Z

m/schema parses the children once

Ben Sless 2021-07-05T11:06:57.311Z

your implementation has no way to cache these results

Ben Sless 2021-07-05T11:07:31.311200Z

It's a bother to do from scratch, you can take this

Ben Sless 2021-07-05T11:07:56.311600Z

Ben Sless 2021-07-05T11:08:06.312Z

thanks slack, very helpful

Richard Hundt 2021-07-05T11:08:38.312200Z

thank you

Ben Sless 2021-07-05T11:09:02.312600Z

there, more convenient

Richard Hundt 2021-07-05T11:09:38.312800Z

yeah, that's pretty much instant now, I'll try it on our production code

Ben Sless 2021-07-05T11:19:52.313Z

πŸ‘

Richard Hundt 2021-07-05T11:36:05.313200Z

Perfect. Thanks Ben! What shall I do with the github issue? Do I close it, or are you planning on making changes to parsing?

ikitommi 2021-07-05T12:43:28.316900Z

I closed the issue, no plans on making the parser faster or caching at the moment. Thanks for @ben.sless for digging into this πŸ™‡

Ben Sless 2021-07-05T12:46:06.317600Z

I think it is worth to leave a comment on the issue and perhaps a note in the readme regarding references to uncompiled schemas

Ben Sless 2021-07-05T12:46:50.317800Z

And you're welcome πŸ™‚ Turns out I was completely wrong regarding the transformers and satisfies?

Ben Sless 2021-07-05T12:47:57.318Z

I see you left a comment with a link to the gist, so that would leave only the readme

ikitommi 2021-07-05T13:14:13.320700Z

if you have idea what and where to write this, please do πŸ™‚

1πŸ‘
ikitommi 2021-07-02T10:50:05.269700Z

@ben.sless, great work. Saw the thread on Clojureverse about compiler optimizations. Would be great if things got automatically or magically faster. While waiting, writing performant code inside the libraries (on the hot perf path) is just being smart.

11πŸ™
ikitommi 2021-07-02T10:54:51.269900Z

Ben Sless 2021-07-02T11:21:35.270700Z

Thank you, I appreciate it. Blame it all on the talk you gave on ClojureTRE 2019

Ben Sless 2021-07-02T11:22:22.271Z

Nice 😁

Ben Sless 2021-07-02T11:35:13.271400Z

If -into-transformer was turned into a protocol, how would you make it work with cljs? fn? there is very different from in clj and won't be covered by a simple extend. Possible to do in cljs what was done for Schemas in clj?

greg 2021-07-02T14:09:43.271800Z

Makes sense. I wouldn't mind losing some of the malli features when instrumented as a spec. Some of the libraries (like integrant's pre-init-spec) support only spec. Although in my case, I use spec mostly for instrumentation of some bits. I was asking for such a conversion, as it sounds like the non-invasive way of trying malli in an existing project. Anyway, I will take a look at malli-instrument. Thanks.

rmcv 2021-07-02T14:53:11.272Z

tried this

(m/decode [:int {:decode/string '(fn [_] (.toEpochMilli (java.time.Instant/now)))}]
          "whatever"
          (merge
           (m/-default-sci-options)
           {:malli.core/sci-options {:classes {'java.time.Instant java.time.Instant}}})
          mt/string-transformer)