-
-
Save ptaoussanis/9931fbc7dc93a8ecc54dd3daaead6c41 to your computer and use it in GitHub Desktop.
A function wrapper that ensures that a function can only be called once
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns scratch.core | |
(:require [taoensso.encore :as enc])) | |
(defn wrap-once-in-a-while-1 | |
"Uses atom + `swap-vals!`" | |
[^long msecs-period f] | |
(let [last-executed_ (atom 0)] | |
(fn wrapper [& args] | |
(let [[old new] | |
(swap-vals! last-executed_ | |
(fn [^long last-executed] | |
(let [now (System/currentTimeMillis) | |
elapsed (- now last-executed)] | |
(if (>= elapsed msecs-period) | |
now | |
last-executed)))) | |
execute? (not= old new)] | |
(when execute? | |
(try | |
(do {:okay (apply f args)}) | |
(catch Throwable t {:error t}))))))) | |
(defn wrap-once-in-a-while-2 | |
"Uses atom + lock" | |
[^long msecs-period f] | |
(let [last-executed_ (atom 0) | |
lock (Object.)] | |
(fn wrapper [& args] | |
(let [execute? | |
(locking lock | |
(let [now (System/currentTimeMillis) | |
elapsed (- now ^long @last-executed_)] | |
(when (>= elapsed msecs-period) | |
(reset! last-executed_ now))))] | |
(when execute? | |
(try | |
(do {:okay (apply f args)}) | |
(catch Throwable t {:error t}))))))) | |
(defn wrap-once-in-a-while-3 | |
"Uses atom + manual `compare-and-set!` loop" | |
[msecs-period f] | |
(let [last-executed_ (atom 0)] | |
(fn wrapper [& args] | |
(let [execute? | |
(loop [] | |
(let [now (System/currentTimeMillis) | |
^long last-executed @last-executed_ | |
elapsed (- now last-executed)] | |
(when (>= elapsed ^long msecs-period) | |
(if (compare-and-set! last-executed_ last-executed now) | |
true | |
(recur)))))] | |
(when execute? | |
(try | |
(do {:okay (apply f args)}) | |
(catch Throwable t {:error t}))))))) | |
(comment | |
(do | |
(def f0 (fn [])) ; Control, without wrapper | |
(def f1 (wrap-once-in-a-while-1 10 (fn []))) | |
(def f2 (wrap-once-in-a-while-2 10 (fn []))) | |
(def f3 (wrap-once-in-a-while-3 10 (fn [])))) | |
;; Quick+dirty single-thread comparative bench | |
(enc/quick-bench 1e6 (f0) (f1) (f2) (f3)) ; [35.98 95.64 66.31 64.26] | |
;; Quick+dirty multi-thread comparative bench | |
(enc/quick-bench 1e4 | |
(let [futures (repeatedly 10 (fn [] (future (f0))))] (run! deref futures)) | |
(let [futures (repeatedly 10 (fn [] (future (f1))))] (run! deref futures)) | |
(let [futures (repeatedly 10 (fn [] (future (f2))))] (run! deref futures)) | |
(let [futures (repeatedly 10 (fn [] (future (f3))))] (run! deref futures))) [507.57 523.9 521.53 517.19] | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A very simple set of comparative benchmarks, just to demo what a comparison might look like.
The key results (times) are
[35.98 95.64 66.31 64.26]
,[507.57 523.9 521.53 517.19]
Some quick conclusions, at least for these numbers:
swap-vals!
implementation is the slowest - at about 140% cost of the lock / CAS implementations.