I would imagine that if @mfikes lookted at this for about five minutes, a lot of these numbers could be halved.
We already looked at this for CLJS-2956, where = caused maximum callstack exceeded. The question is if you want to optimize spec for self spec’ing: you basically have to write entire spec in a different language if you want to solve this problem once and for all (JavaScript).
I tried this:
apply' (fn [f args]
(if (and (nil? args)
pure-variadic?)
(.cljs$core$IFn$_invoke$arity$variadic f)
(do (m/unstrument `apply)
(apply f args)
#_(m/instrument `apply))))
in spec-checking-fn
, but that didn’t make
(stest/instrument `apply)
(apply + 1 2 [3])
work in user code 🙂Disabled apply
instrumentation in test on cljs. Pushed to cljs-1.10.439
branch.
Tests pass on clj and planck, but not on cljs. Still seeing CLJS-2955 despite @mfikes’s patches applied.
it appears to be an issue with the cljs-test-runner
. When I run the tests using our own script, they are fine
Merged the cljs-1.10.439 branch and reverted to nashorn instead of the test-runner.
back to node now. it’s so much faster
build / test time goes from 2:28 to 0:52 again
@slipset one note, now that we’re depending on a git dep (temporary patches for CLJS), the lein setup is broken now (especially lein deploy
). I saw that you made a thing for clj
to deploy to clojars. Maybe we could use that?
Yup, haven’t gotten around to that yet 🙂
🙂
If there are specs that require non-released ClojureScript versions in order to work, perhaps they could be conditionally enabled, either using :closure-defines
or goog.string/compareVersions
comparing *clojurescript-version*
.
That way, Speculative can be more readily used in downstream projects.
sounds like an idea. but specs only become errors in certain situations: e.g. when you instrument them, or generatively test them.
so it depends?
the overhead of internal spec calls using the spec-checking-fn
may be OK. I discovered that conforming only happens the amount of times you call the function yourself when instrumenting.
(def counter (atom 0))
(defn fixture [f]
(reset! counter 0)
(f))
(t/use-fixtures :each fixture)
(deftest =-cost-test
(s/fdef clojure.core/=
:args (fn [_#]
(println _#)
(swap! counter inc)
_#))
(with-instrumentation `=
(println "START")
(apply = 1 [1])
(apply = 2 [1])
(println "COST:" @counter))
(println "AFTER INSTRUMENT")
(println @counter))
The cost for =
here was 2, but the wrapped version of =
got called 73 times. So there isn’t the overhead of conforming each internal call of =
, that’s good. But there is still some small overhead which may be OK.
The only way to know is to do a perf test with and without. 🙂 http://blog.fikesfarm.com/posts/2017-11-18-clojurescript-performance-measurement.html
cljs.user=> (simple-benchmark [] (= 1 2 3 4 5 6) 100000)
[], (= 1 2 3 4 5 6), 100000 runs, 28 msecs
nil
cljs.user=> (require '[speculative.core])
nil
cljs.user=> (stest/instrument `=)
[cljs.core/=]
cljs.user=> (simple-benchmark [] (= 1 2 3 4 5 6) 100000)
[], (= 1 2 3 4 5 6), 100000 runs, 31902 msecs
nil
using this method, we could score the specs (31902/28 = 1139) and give it a performance cost.
so we could give users a choice: instrumenting =
, go ahead (and given it’s accepting nature there’s almost no way to make it crash so not very useful), but here’s the cost.
hmm:
$ plk -A:test:repl
cljs.user=> (require '[speculative.core])
nil
cljs.user=> (stest/instrument `str)
Maximum call stack size exceeded.
merge
‘only’ has a cost factor of 82 and it’s also a pretty useful spec. so a typical dev profile would select that one