sci

https://github.com/babashka/SCI - also see #babashka and #nbb
ikitommi 2020-07-20T07:45:38.281700Z

is the ctx effective immutable? what if it could be created in advance?

ikitommi 2020-07-20T07:46:16.281800Z

ikitommi 2020-07-20T07:48:31.283600Z

this kind of api would be sweet:

(def my-eval (sci.api/evaluator {:preset ..., :bindings ..., :namespaces ...})

(my-eval "(+ 1 1)")
; => 2

👍 1
borkdude 2020-07-20T07:56:41.285Z

@ikitommi This exists. It works like this:

(def ctx (sci/init {:preset ...}))
(sci/eval-string* ctx "(+ 1 1)")
However, the ctx is not immutable, it remembers all effects (defs, etc).

borkdude 2020-07-20T07:57:12.285200Z

This is used for REPLs, etc.

borkdude 2020-07-20T07:58:51.285700Z

I think I'm missing the point since you already have eval-string* in your benchmark as well.

ikitommi 2020-07-20T07:59:30.286400Z

if the ctx is not immutable, the eval-string* should not be used?

ikitommi 2020-07-20T07:59:52.286700Z

… with reused ctx.

ikitommi 2020-07-20T08:04:15.290Z

if sci could create a immutable prototype of ctx ahead-of-time out of options, e.g. with sci.api/evaluator, when calling it at runtime, it would just add the mutable part in to make a full mutabl ctx, might same some micros?

borkdude 2020-07-20T08:04:48.290800Z

@ikitommi Yes, it should be used with re-used ctx in a lot of cases, that's exactly the point, e.g. in a REPL

ikitommi 2020-07-20T08:06:58.292700Z

hmm. but my case is non-repl, would benefit of not re-creating the full ctx on every eval? looking at the code, most of the ctx is immutable and doesn’t need to be re-created on every call?

borkdude 2020-07-20T08:07:47.293600Z

what could go wrong in your case if you did re-use the stateful ctx?

ikitommi 2020-07-20T08:10:39.294700Z

(def ctx (scio/init {:preset :termination-safe}))

;; customer 1 writes using a web-ui
(scii/eval-string* ctx "(def company1-secret 123)")

;; customer 2 reads it
(scii/eval-string* ctx "{:hacked company1-secret}")
; => {:hacked 123}

ikitommi 2020-07-20T08:12:57.295600Z

this is, if you evaluate sci-strings in a shared environment, e.g. on the backend.

ikitommi 2020-07-20T08:13:20.296300Z

and persist things into db.

borkdude 2020-07-20T08:14:14.297100Z

So a new API could save you 9µs at most. Not sure if that's worth it?

2020-07-20T08:15:17.298800Z

I have another usecase, but I agree with @ikitommi that for safety reasons it would be worth it. Although if you give up performance (and in my case it would be more) you could fix that

2020-07-20T08:16:19.300300Z

I’m currently thinking of working around this by not allowing def and other mutable operations, but a immutable snapshot of ctx would be nice for me

ikitommi 2020-07-20T08:17:07.301200Z

the perf is not that important here, just testing it. Coudn’t find the issue about “lean api”, was pretty sure there was one. idea that you could have an api, that would NOT include the default namespace mappings and would return an optimized eval function. One could give all the var-bindings as options to evaluator.

ikitommi 2020-07-20T08:18:41.302900Z

currently, malli.core loads ~500ms, sci.core with defaults adds 2000ms to it and makes the js-bundle quite big. trying to integrate dynaload in, so that one could control if sci should be budled in or not. But, still, it’s all (big) or nothing.

borkdude 2020-07-20T08:19:27.303600Z

@ikitommi This is that issue: https://github.com/borkdude/sci/issues/357

👍 1
ikitommi 2020-07-20T08:21:00.305600Z

having a custom api would allow me to write malli.sci, which would have a relevant subset of sci, that would be: • usefull enough • load faster • create small (enough) bundles on js • be bit faster at runtime (the 9micros per)

borkdude 2020-07-20T08:22:03.306500Z

I think issue 357 would help in getting leaner builds, not sure about initial load time though, since still the parser based on tools.reader, etc. etc. have to be loaded

borkdude 2020-07-20T08:23:37.307400Z

@dominicm has released a tool yesterday which shows load time per namespace. This could be used to get a sense of where the load time goes currently: https://sr.ht/~severeoverfl0w/slow-namespace-clj/

ikitommi 2020-07-20T08:26:15.308200Z

(time (require '[sci.impl.namespaces]))
"Elapsed time: 2018.835938 msecs"

borkdude 2020-07-20T08:27:45.309200Z

🙂 There is a suggestion in the issue how you can experiment with leaner builds right now, by just overriding the sci.impl.namespaces file in your project. Might be worth taking a stab at that. When this approach works, maybe we can make some nice API that will generate this file for you.

2020-07-20T08:28:37.310100Z

Am I right that there are two things being mixed here: immutable ctx and lean builds?

borkdude 2020-07-20T08:29:16.310500Z

Yes. It's a bit of a fragmented discussion 🙂

2020-07-20T08:29:25.310700Z

ok just checking 🙂 Maybe I was missing something

borkdude 2020-07-20T08:32:02.312100Z

Btw, I have a quick workaround for the immutable ctx here:

(def mutable-ctx (sci/init {:preset :termination-safe}))
(def immutable-ctx (update mutable-ctx :env deref))
(defn init-ctx [immutable-ctx] (update immutable-ctx :env atom))
(sci/eval-string* (init-ctx proto-ctx) "(+ 1 2 3)" )
;;=> 6
(sci/eval-string* (init-ctx proto-ctx) "(def x 1) x")
1
(sci/eval-string* (init-ctx proto-ctx) "x")
Execution error (ExceptionInfo) at sci.impl.utils/throw-error-with-location (utils.cljc:54).
Could not resolve symbol: x [at line 1, column 1]
This can be used for benchmarking, to check how much this actually saves in performance.

1
borkdude 2020-07-20T08:32:47.312400Z

(edited)

2020-07-20T08:34:14.313700Z

Ah smart. I’ll use this 🙂 For me it’s about caching between requests. So a small saving could make a big difference with a lot of traffic. But I think this example would fix that

borkdude 2020-07-20T08:39:46.314200Z

Made an issue here: https://github.com/borkdude/sci/issues/369

2020-07-20T08:40:08.314600Z

Thanks!

ikitommi 2020-07-20T08:43:14.315200Z

(ns user
  (:require [criterium.core :as cc]
            [sci.core :as sci]
            [sci.impl.interpreter :as scii]
            [sci.impl.opts :as scio]))

;; 25µs
(cc/quick-bench (sci/eval-string "(+ 1 1)" nil))

(def ctx (update (scio/init nil) :env deref))

;; 13µs
(cc/quick-bench (scii/eval-string* (update ctx :env atom) "(+ 1 1)"))

🚀 1
ikitommi 2020-07-20T08:44:15.315800Z

and thanks for @dominicm for slow, here’s from malli:

clj-time : 435.44744099999997 msecs
clojure : 1200.8413420000002 msecs
clojure.core : 146.148892 msecs
clojure.core.specs : 48.750516 msecs
clojure.java : 49.91768999999999 msecs
clojure.spec : 28.553624 msecs
clojure.spec.gen : 0.386083 msecs
clojure.spec.test : 27.847193 msecs
clojure.test : 446.680308 msecs
clojure.test.check : 402.867701 msecs
clojure.test.check.clojure-test : 12.399544 msecs
clojure.tools : 331.46736000000004 msecs
clojure.tools.namespace : 276.347247 msecs
clojure.tools.reader : 24.264338 msecs
clojure.tools.reader.impl : 1.6841169999999999 msecs
com.gfredericks.test.chuck : 510.495037 msecs
com.gfredericks.test.chuck.regexes : 45.83087 msecs
edamame : 47.460241 msecs
edamame.impl : 43.228698 msecs
malli : 870.768437 msecs
sci : 1624.3018359999999 msecs

dominicm 2020-07-20T08:45:38.316200Z

You're more than welcome, it was a fun little tool to write. I've had it sitting in my snippets for ages from doing debug. It's a fun game to play.

borkdude 2020-07-20T08:58:56.316400Z

What about more specifics? sci consists of multiple namespaces

borkdude 2020-07-20T09:15:23.316600Z

I'm surprised that clojure.test takes more time to load than clojure.core. Is that maybe because clojure.core is AOT-ed and clojure.test is not?

borkdude 2020-07-20T09:15:50.316900Z

Or maybe the benchmark isn't that reliable?

dominicm 2020-07-20T09:37:48.317100Z

Things like AOT are not factored by the tool.

borkdude 2020-07-20T09:38:18.317300Z

I believe the tool works, I'm just wondering about the results I'm seeing

dominicm 2020-07-20T09:38:30.317500Z

It's definitely curious.

dominicm 2020-07-20T09:38:42.317700Z

Oh

dominicm 2020-07-20T09:38:49.317900Z

Just realized, so these are the groups

dominicm 2020-07-20T09:39:26.318100Z

So it's clojure.test and clojure.test.check, etc all bundled up

ikitommi 2020-07-20T09:47:33.318300Z

sci.addons : 6.825647 msecs
sci.impl.unrestrict : 9.062802 msecs
sci.impl.main : 12.817414 msecs
sci.impl.parser : 16.701379 msecs
sci.impl.opts : 19.185607 msecs
sci.impl.records : 19.598105 msecs
sci.impl.multimethods : 20.508318 msecs
sci.impl.hierarchies : 20.85216 msecs
sci.impl.doseq-macro : 21.41064 msecs
sci.impl.macros : 22.040018 msecs
sci.addons.future : 25.766132 msecs
sci.impl.interop : 29.472987 msecs
sci.impl.fns : 31.424244 msecs
sci.core : 33.194666 msecs
sci.impl.max-or-throw : 33.917608 msecs
sci.impl.destructure : 37.038555 msecs
sci.impl.for-macro : 38.409595 msecs
<http://sci.impl.io|sci.impl.io> : 48.15481 msecs
sci.impl.protocols : 49.136158 msecs
sci.impl.types : 50.514564 msecs
sci.impl.utils : 52.204186 msecs
sci.impl.interpreter : 152.375188 msecs
sci.impl.analyzer : 155.308065 msecs
sci.impl.vars : 236.363401 msecs
sci.impl.namespaces : 694.504691 msecs

ikitommi 2020-07-20T09:48:02.318500Z

tested with empty sci.impl.namespaces too:

sci.addons : 8.37424 msecs
sci.impl.main : 13.100328 msecs
sci.impl.namespaces : 14.493458 msecs
sci.impl.unrestrict : 19.593454 msecs
sci.impl.doseq-macro : 28.334558 msecs
sci.impl.macros : 35.316999 msecs
sci.impl.multimethods : 36.71492 msecs
sci.impl.parser : 37.466406 msecs
sci.addons.future : 39.400002 msecs
sci.impl.records : 39.564858 msecs
sci.impl.destructure : 45.046122 msecs
sci.impl.hierarchies : 47.945577 msecs
sci.impl.interop : 50.139426 msecs
sci.impl.opts : 53.413407 msecs
sci.impl.for-macro : 58.779449 msecs
sci.impl.fns : 65.738822 msecs
sci.impl.max-or-throw : 69.958636 msecs
<http://sci.impl.io|sci.impl.io> : 73.913664 msecs
sci.impl.types : 88.955042 msecs
sci.impl.protocols : 89.97283 msecs
sci.impl.utils : 111.602038 msecs
sci.core : 123.166932 msecs
sci.impl.analyzer : 329.678756 msecs
sci.impl.vars : 348.128747 msecs
sci.impl.interpreter : 409.58255 msecs

ikitommi 2020-07-20T09:48:47.318700Z

it totals actually to more, guess there is variance, but as the namespaces load other ns’es, the order might matter here?

borkdude 2020-07-20T09:50:06.318900Z

so what does this tell us about sci.impl.namespaces?

borkdude 2020-07-20T09:50:27.319100Z

maybe run this in criterium as well? 😉

borkdude 2020-07-20T09:50:47.319300Z

you can use :reload-all to force reloading of namespaces

ikitommi 2020-07-20T09:50:53.319500Z

another round with just totals: 1. empty namespaces:

sci : 1244.0422230000001 msecs
sci.addons : 15.879876 msecs
sci.impl : 1161.9842830000005 msecs
2. default namespaces:
sci : 1836.7869400000002 msecs
sci.addons : 25.766132 msecs
sci.impl : 1771.0004950000002 msecs

ikitommi 2020-07-20T09:51:05.319700Z

yes, the test is flawed at least 🙂

ikitommi 2020-07-20T09:53:01.319900Z

but, with the new clojure client compile thing, I think the JVM load time is not a big problem, that will go away with 3rd party tooling (compiling all non-project files ahead-of-time).

borkdude 2020-07-20T09:57:51.320100Z

new clojure client compile thing?

ikitommi 2020-07-20T10:00:38.320300Z

running the compile manually and adding the compilation results into classpath: https://clojure.org/guides/dev_startup_time

borkdude 2020-07-20T10:01:05.320500Z

right

ikitommi 2020-07-20T10:01:06.320700Z

looking for some tooling on top of this, should only compile external dependencies.

ikitommi 2020-07-20T10:02:11.320900Z

tried in a big project, dropped compilation from 30sec to 4sec, but fails if the project files are compiled too, as the changes are not captured after that. for immutable / external files, should be good

ikitommi 2020-07-20T10:05:08.321100Z

ikitommi 2020-07-20T10:05:35.322Z

sci bundle with default namespaces. with empty (no vars binded) namespaces:

ikitommi 2020-07-20T10:05:45.322100Z

borkdude 2020-07-20T10:21:16.322500Z

I see 3 different numbers here. Gzipped vs not gzipped, but also optimized. What does optimized mean?

dominicm 2020-07-20T11:39:02.322700Z

What you said about changes is not correct. Clojure uses the last modified time to decide.

dominicm 2020-07-20T11:39:48.322900Z

Order is handled by my tool, it builds a dependency graph. But only using the ns macro, so will not work with dynamic requires.

dominicm 2020-07-20T11:41:07.323100Z

The core team is working on automating compile, as clj gets involved. I'd started working on a tool, but Alex said he was already and there were core problems that needed solving before it could work reliably.

👍 1
dominicm 2020-07-20T11:41:41.323300Z

I believe tools namespace cannot handle aot artefacts, which may explain your problems.

dominicm 2020-07-20T12:09:54.323500Z

I think that's the impact of Google closure

ikitommi 2020-07-20T12:11:12.323700Z

might be that. Things broke on integrant reset

dominicm 2020-07-20T12:12:27.324Z

That would be it then. I haven't investigated why it has issues. It touches on internals a little, so I expect it will be frustrating to discover.