Skip to content

Commit

Permalink
Faster promesa.exec/wrap-bindings
Browse files Browse the repository at this point in the history
83x faster than current implementation 
```
(run-benchmark
  (fn []
    (testing "`promesa.exec.csp` implementation"
      ;; 4.956172 µs — this is due to `promesa.exec/wrap-bindings`'s way of forwarding
      ;; bindings, which is 83x slower than `clojure.core/binding-conveyor-fn`
      (criterium/bench
        (csp/go))
      ;; 4.889489 ms
      (criterium/bench
        (dotimes [i 1000]
          (csp/go))))
    (testing "Bare implementation"
      ;; 59.033042 ns
      (criterium/bench
        (-> (Thread/ofVirtual)
            (.unstarted (fn []))
            (.start))))
    (testing "With bindings forwarding"
      ;; 58.851215 ns
      (criterium/bench
        (-> (Thread/ofVirtual)
            (.unstarted (binding-conveyor (^{:once true} fn* [] ~@Body)))
            (.start))))))
```
  • Loading branch information
alexandergunnarson authored Nov 18, 2024
1 parent be4ea19 commit c099142
Showing 1 changed file with 48 additions and 46 deletions.
94 changes: 48 additions & 46 deletions src/promesa/exec.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -179,56 +179,58 @@
@default-scheduler
(pu/maybe-deref scheduler))))

#?(:clj
(defn- binding-conveyor-inner
[f]
(let [frame (clojure.lang.Var/cloneThreadBindingFrame)]
(fn
([]
(clojure.lang.Var/resetThreadBindingFrame frame)
(cond
(instance? clojure.lang.IFn f)
(.invoke ^clojure.lang.IFn f)

(instance? java.lang.Runnable f)
(.run ^java.lang.Runnable f)

(instance? java.util.concurrent.Callable f)
(.call ^java.util.concurrent.Callable f)

:else
(throw (ex-info "Unsupported function type" {:f f :type (type f)}))))
([x]
(clojure.lang.Var/resetThreadBindingFrame frame)
(cond
(instance? clojure.lang.IFn f)
(.invoke ^clojure.lang.IFn f x)

(instance? java.util.function.Function f)
(.apply ^java.util.function.Function f x)

:else
(throw (ex-info "Unsupported function type" {:f f :type (type f)}))))
([x y]
(clojure.lang.Var/resetThreadBindingFrame frame)
(f x y))
([x y z]
(clojure.lang.Var/resetThreadBindingFrame frame)
(f x y z))
([x y z & args]
(clojure.lang.Var/resetThreadBindingFrame frame)
(apply f x y z args))))))

(defn wrap-bindings
{:no-doc true}
;; Passes on local bindings from one thread to another. Compatible with `clojure.lang.IFn`,
;; `java.lang.Runnable`, `java.util.concurrent.Callable`, and `java.util.function.Function`.
;; Adapted from `clojure.core/binding-conveyor-fn`."
[f]
#?(:cljs f
:clj
(let [bindings (get-thread-bindings)]
(fn
([]
(push-thread-bindings bindings)
(try
(f)
(finally
(pop-thread-bindings))))
([a]
(push-thread-bindings bindings)
(try
(f a)
(finally
(pop-thread-bindings))))
([a b]
(push-thread-bindings bindings)
(try
(f a b)
(finally
(pop-thread-bindings))))
([a b c]
(push-thread-bindings bindings)
(try
(f a b c)
(finally
(pop-thread-bindings))))
([a b c d]
(push-thread-bindings bindings)
(try
(f a b c d)
(finally
(pop-thread-bindings))))
([a b c d e]
(push-thread-bindings bindings)
(try
(f a b c d e)
(finally
(pop-thread-bindings))))
([a b c d e & args]
(push-thread-bindings bindings)
(try
(apply f a b c d e args)
(finally
(pop-thread-bindings))))))))

:clj (reify
clojure.lang.IFn
(invoke [_ f] (binding-conveyor-inner f))
java.util.function.Function
(apply [_ f] (binding-conveyor-inner f)))))

#?(:clj
(defn thread-factory?
Expand Down

0 comments on commit c099142

Please sign in to comment.