-
-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider mutable bindings map for perf #416
Comments
I have considered using mutable bindings, but this will complicate multi-threaded code. Not sure how to handle this and it if's worth it yet... |
I think concurrent hashmap would probably be okay, but who knows. |
If we would have one concurrent hash-map for the entire environment, what would happen in the following: (let [x 1] ;; push bindings onto concurrent hash-map
(future (Thread/sleep 1000) x) ;; try to resolve x from concurrent hash-map... but already has been popped?
) ;; end of body, pop bindings from concurrent hash-map |
Good point. I did not include futures and similar threading in my original scope of thought for the interpreter. Immutable bindings definitely simplify this process. For the single threaded context, mutable bindings wouldn't be a problem. Alternately, detecting when threads and doing some copying (no idea how cost prohibitive this would be). Hmm. |
@joinr I think InheritableThreadLocal might offer a way here:
|
A better approached is demonstrated in uclj by @erdos and I think that approach can be successfully translated to SCI. It uses two arrays for a function: one contains the closed over environment (let's call it This should give us a 3x-10x or so speedup for loops compared to the immutable map approach. |
I'm not an interpreter viking, but is there any tradeoff to going for arrays vs a mutable map? I guess if you can perfectly resolve all references and statically bind them to indices then you should be fine right? Arrays also open up some possibilities for primitive args and results. |
Ah I think I see what's going on w.r.t. compile-time lexical analysis and variable resolution. similar to https://craftinginterpreters.com/local-variables.html |
@joinr I suspect using pre-allocated arrays will have even better performance. user=> (time (let [opts {:a :foo}] (dotimes [i (Math/pow 10 9)] (:a opts))))
"Elapsed time: 4674.451531 msecs"
nil
user=> (defrecord Foo [a])
user.Foo
user=> (time (let [opts (->Foo 1)] (dotimes [i (Math/pow 10 9)] (:a opts))))
"Elapsed time: 2153.796838 msecs"
nil
user=> (time (let [opts (java.util.HashMap. {:a 1})] (dotimes [i (Math/pow 10 9)] (.get ^java.util.Map opts :a))))
"Elapsed time: 3083.784156 msecs"
nil
user=> (time (let [opts (into-array [:foo :bar])] (dotimes [i (Math/pow 10 9)] (aget ^objects opts 0))))
"Elapsed time: 267.298268 msecs" |
IIRC it's always a bit faster to invoke the map as a function instead of keyword-as-function (I'm on java 8, ymmv):
Arrays appear to edge out field access on classes a bit (judging by sub-nanos though...) |
Merged a first pass of using mutable arrays for bindings to master. Loops are roughly 2-4 faster (depending on JVM, GraalVM, v8). |
See joinr@5b18289
Needs perf benchmarking.
cc @joinr
The text was updated successfully, but these errors were encountered: