Skip to content

Instantly share code, notes, and snippets.

@lynaghk
Created August 12, 2012 22:56
Show Gist options
  • Save lynaghk/3335154 to your computer and use it in GitHub Desktop.
Save lynaghk/3335154 to your computer and use it in GitHub Desktop.
content-based pubsub via core.match. Faster than I expected!
(ns crossfilter.busses
(:use-macros [c2.util :only [p pp]]
[clojure.core.match.js :only [match]]
[crossfilter.macros :only [subscribe!]])
(:require [clojure.string :as str]
[goog.pubsub.PubSub :as goog.pubsub.PubSub]
[goog.object :as gobj]))
(set! *print-fn* #(.log js/console %))
(def n "Number of messages" 100000)
(def m "Number of subscribers" 20)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;Hand-rolled regex-based pubsub
(let [!subscribers (js-obj)
publish! (fn [topic & args]
(gobj/forEach !subscribers
(fn [re fs]
(when (.match topic re)
(.forEach fs #(apply % args))))))
subscribe! (fn [topic f]
(let [topic-re (re-pattern (str "^" (-> topic
(str/replace "*" "[^/]*"))
"$"))]
(aset !subscribers
topic-re
(let [a (or (aget !subscribers topic-re)
(array))]
(.push a f)
a))))]
(dotimes [_ m]
(subscribe! "my-topic" (fn [])))
(print "approx pubsub")
(time
(dotimes [i n]
(publish! "my-topic"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;Google Closure's exact string pubsub
(let [chan (goog.pubsub.PubSub.)]
(dotimes [_ m]
(.subscribe chan "my-topic" (fn [])))
(print "goog.pubsub")
(time
(dotimes [i n]
(.publish chan "my-topic"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;
;;Core.match-based pubsub
(def *!subscribers* (atom []))
(defn publish! [m]
(doseq [s @*!subscribers*]
(s m)))
;; Tested in CLJS; this macro defined in a CLJ file.
;;
;; (defmacro subscribe! [pattern & body]
;; `(swap! ~'*!subscribers* conj
;; (fn [m#]
;; (match [m#]
;; [~pattern] (do ~@body)))))
(do (print "match pubsub with string matching")
(dotimes [_ m]
(subscribe! "my-topic"
"no-op"))
(time
(dotimes [i n]
(publish! "my-topic"))))
(reset! *!subscribers* [])
(do (print "match pubsub with map destructuring")
(dotimes [_ m]
(subscribe! {:with x}
"no-op"))
(time
(dotimes [i n]
(publish! {:with "destructuring"}))))
;;Results from a MacBook Air, Closure advanced mode compilations.
;; approx pubsub
;; "Elapsed time: 16686 msecs"
;; goog.pubsub
;; "Elapsed time: 1417 msecs"
;; match pubsub with string matching
;; "Elapsed time: 583 msecs"
;; match pubsub with map destructuring
;; "Elapsed time: 787 msecs"
@lynaghk
Copy link
Author

lynaghk commented Aug 12, 2012

@dnolen Content-based map-destructuring pubsub via core.match in CLJS is faster than Google's exact string matching pubsub. Wait, what? Awesome.

@ohpauleez
Copy link

How is that even possible?!

I should totally try this on Shoreleave's base pubsub protocol implementations.

@lynaghk
Copy link
Author

lynaghk commented Aug 13, 2012

Yeah, right? Maybe there's some laziness tricks going on, but I've run this using counters inside of the subscribing functions to make sure they're getting called, and it appears to be working.
Maybe things will get hella slower with more complicated pattern matching?

@lynaghk
Copy link
Author

lynaghk commented Aug 13, 2012

@ohpauleez Also, WTF is up with the terrible regex performance? I moved everything to use JS data structures over ClojureScript's seqs, but it's still hella slow.

@lynaghk
Copy link
Author

lynaghk commented Aug 13, 2012

Uh oh. Turns out core.match will match variables in a map to nil.
That is, (subscribe! {:with x} ...)) will get called if any map is published, binding x to nil.

@lynaghk
Copy link
Author

lynaghk commented Aug 14, 2012

Patched; awaiting merge into master: https://github.com/lynaghk/core.match/tree/issue-52

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