Wednesday, March 7, 2012

Thoughts on Protocols

In the past week or so, I've been researching how to implement protocols in Clojure-Py. In order to implement protocols, we need a efficient way to dispatch a function on a given type. A simple solution would be something like this:


(def impls {py/int print-int
                      py/float print-float
                      mytype print-mytype})

(defn print [x & args]
      (apply (get impls (py/type x)) x & args))

While this may look fairly simple, we have one problem. Our implementation will be quite slow on PyPy compared to calling a normal bound method (e.g. foo.bar). This is because PyPy assumes that we will not often modify a variable's type, and it optimizes class attributes accordingly. Therefore this would be much better:

(defn install-fn [type name fn]
     (setattr type (str "__proto__" name) fn))

(install-fn py/int "print" print-int)
(install-fn py/float "print" print-float)
(install-fn mytype "print" print-mytype)

(defn print [x & args]
     (apply  (.-__proto__print (py/type x)) x & args))

This method will be extremely fast, but has one flaw: we can't modify built-in classes. So the solution is to do a hybrid. We will attempt to modify the class with setattr, if that fails, we'll save the function to a hash-map. This way PyPy can optimize away for all our custom types, and everything else will have to hit the hashmap. From my benchmarks, this hybrid approach is, about 2x faster than using a straight hashmap.
 

No comments:

Post a Comment