Skip to content

Instantly share code, notes, and snippets.

@danlentz
Last active August 29, 2015 14:14
Show Gist options
  • Save danlentz/301a0f056ffd9c6605a9 to your computer and use it in GitHub Desktop.
Save danlentz/301a0f056ffd9c6605a9 to your computer and use it in GitHub Desktop.
Thread-safe Monotonic Clock for Clojure
(ns clock)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Lock-Free Thread-safe Monotonic Clock
;; Dan Lentz <http://github.com/danlentz>
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; We use epoch stamp (milliseconds) and zero-pad the low order bits
;; of millisecond stamp and provide atomic incremental
;; uuids-this- second subcounter on the low order bits, which
;; guarantee that two timestamps never collide regardless of clock
;; precision.
;;
;; 113914335216380000 (+ (* (epoch-time) 10000) 100103040000000000)
;; 113914335216380001 first contending timestamp
;; 113914335216380002 second contending timestamp
;; ... and so forth
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:const +subcounter-resolution+ 9999)
(deftype State [^short seqid ^long millis])
(let [-state- (atom (->State 0 0))]
(defn monotonic-time ^long []
(let [^State new-state
(swap! -state-
(fn [^State current-state]
(loop [time-now (System/currentTimeMillis)]
(if-not (= (.millis current-state) time-now)
(->State 0 time-now)
(let [tt (.seqid current-state)]
(if (< tt +subcounter-resolution+)
(->State (inc tt) time-now)
(recur (System/currentTimeMillis))))))))]
(+ (.seqid new-state)
(+ (* (.millis new-state) 10000) 100103040000000000)))))
@sbocq
Copy link

sbocq commented Feb 3, 2015

@danlentz revision 12 runs also about the same speed (250ms) in my simple benchmark.

And without going through any hoops this idiomatic one takes also about 250ms!

(let [max-stamps 9999
      state (atom [0 0])]
  (defn monotonic-time ^long []
    (let [[now stamps] (swap! state
                              (fn [[last-time stamps]]
                                (let [now (System/currentTimeMillis)]
                                  (if (= now last-time)
                                    (if (< stamps max-stamps)
                                      [now (inc stamps)]
                                      (recur state))
                                    [now 0]))))]
      (+ (* now 10000) 100103040000000000 stamps))))

@danlentz
Copy link
Author

danlentz commented Feb 3, 2015

interesting discussion, @sbocq! Thanks for joining in!

@sbocq
Copy link

sbocq commented Feb 3, 2015

@danlentz Thanks! I'm looking forward for more ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment