Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link
Owner Author

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

This comment has been minimized.

Copy link

commented Aug 13, 2012

How is that even possible?!

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

@lynaghk

This comment has been minimized.

Copy link
Owner Author

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

This comment has been minimized.

Copy link
Owner Author

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

This comment has been minimized.

Copy link
Owner Author

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

This comment has been minimized.

Copy link
Owner Author

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
You can’t perform that action at this time.