Bunch of comments here inline below...
On Thursday, May 12, 2016 at 2:58:29 PM UTC-5, JvJ wrote: > > I've been doing some performance tests on various floating point > operations. In particular, I wanted to check what would be the fastest way > to implement a 2-d floating point vector in clojure. > > Here's my tests: > > ;;; V+ Tests > ;;; Implementing floating-point vectors as: > ;;; structs, deftype, vector > (defrecord v2r > [^Double x ^Double y]) > > These type hints don't actually do much for you - the record will always have field of type Object. The one exception to this are the two primitive type hints ^double and ^long - using ^double here would help you. Type hints on a record field type do assist with type inference for methods declared inside the record (but you don't have those). (defn rv+ > [^v2r v1 ^v2r v2] > (v2r. (+ (.x v1) (.x v2)) > (+ (.y v1) (.y v2)))) > > In general, it is considered bad form to use the java interop features to construct or access fields of a record. Preferred: (defn rv+ [v1 v2] (->v2r (+ (:x v1) (:x v2)) (+ (:y v1) (:y v2)))) > (deftype v2t > [^Double x ^Double y]) > > Ditto above - primitive type hints would help. > > (defn tv+ > [^v2t v1 ^v2t v2] > (v2t. (+ (.x v1) (.x v2)) > (+ (.y v1) (.y v2)))) > > Ditto above although keyword accessors are not provided with deftypes. If you are accessing fields directly, use .- to indicate field access. (defn tv+ [^v2t v1 ^v2t v2] (->v2t. (+ (.-x v1) (.-x v2)) (+ (.-y v1) (.-y v2)))) However, it's generally better to define an interface, then have the type v2t implement that interface. The other benefit of this is that the interface can specify primitive accessors and the type will implement them accordingly - this eliminates boxing on the accessors. (definterface IV (^double getX []) (^double getY [])) (deftype v2t [^double x ^double y] IV (getX [] x) (getY [] y)) > (defn vv+ > [v1 v2] > (mapv + v1 v2)) > > mapv (and any sequence or collection ops) use only boxed objects so this is going to foil all the carefully laid plans above. If you do the more manual path and type hint sufficiently (I'm not actually running any of this, so you may not actually need these hints here): (defn vv+ [^v2t v1 ^v2t v2] (->v2t (+ ^double (getX v1) ^double (getX v2)) (+ ^double (getY v1) ^double (getY v2)))) > > (defn testvecs > "Tests the vector add operations by reducing a > random list by each of the v+ operations on > identical sets of random vectors" > [n] > (let [nums (repeatedly (* 2 n) rand) > vpairs (partition 2 nums) > v2r-list (apply list (map (fn [[x y]] (v2r. x y)) vpairs)) > v2t-list (apply list (map (fn [[x y]] (v2t. x y)) vpairs)) > v2v-list (apply list (map vec vpairs))] > (println "V2 Record:") > (bench (reduce rv+ v2r-list)) > (println "V2 Type:") > (bench (reduce tv+ v2t-list)) > (println "V2 Vector:") > (bench (reduce vv+ v2v-list)) > (println "Just Doubles:") > (bench (reduce + nums)))) > > > And some other options to compare beyond this are primitive vectors (see vector-of) which gives you the memory usage of arrays, but still boxes on use (for now! but maybe not forever) and of course Java arrays, which are how this is probably best done to prevent all boxing. And then there are libs like hiphip (https://github.com/plumatic/hiphip) for better working with arrays or core.matrix (for access to native vector math). I suspect those paths will substantially improve perf, perhaps by 100x more than you're seeing even in your good numbers now. > > Here's my output: > > *V2 Record:* > Evaluation count : 420 in 60 samples of 7 calls. > Execution time mean : 164.379725 ms > Execution time std-deviation : 4.478128 ms > Execution time lower quantile : 149.546749 ms ( 2.5%) > Execution time upper quantile : 172.317132 ms (97.5%) > Overhead used : 1.873322 ns > > Found 8 outliers in 60 samples (13.3333 %) > low-severe 4 (6.6667 %) > low-mild 3 (5.0000 %) > high-mild 1 (1.6667 %) > Variance from outliers : 14.2105 % Variance is moderately inflated by > outliers > *V2 Type:* > Evaluation count : 1860 in 60 samples of 31 calls. > Execution time mean : 32.238857 ms > Execution time std-deviation : 2.331682 ms > Execution time lower quantile : 26.769206 ms ( 2.5%) > Execution time upper quantile : 35.318368 ms (97.5%) > Overhead used : 1.873322 ns > > Found 8 outliers in 60 samples (13.3333 %) > low-severe 5 (8.3333 %) > low-mild 3 (5.0000 %) > Variance from outliers : 53.4940 % Variance is severely inflated by > outliers > *V2 Vector:* > Evaluation count : 2040 in 60 samples of 34 calls. > Execution time mean : 30.175015 ms > Execution time std-deviation : 3.870306 ms > Execution time lower quantile : 21.877116 ms ( 2.5%) > Execution time upper quantile : 37.668717 ms (97.5%) > Overhead used : 1.873322 ns > > Found 10 outliers in 60 samples (16.6667 %) > low-severe 5 (8.3333 %) > low-mild 2 (3.3333 %) > high-mild 3 (5.0000 %) > Variance from outliers : 78.9869 % Variance is severely inflated by > outliers > *Just Doubles:* > Evaluation count : 20640 in 60 samples of 344 calls. > Execution time mean : 4.309871 ms > Execution time std-deviation : 537.444033 µs > Execution time lower quantile : 2.389294 ms ( 2.5%) > Execution time upper quantile : 4.716009 ms (97.5%) > Overhead used : 1.873322 ns > > Found 8 outliers in 60 samples (13.3333 %) > low-severe 5 (8.3333 %) > low-mild 2 (3.3333 %) > high-mild 1 (1.6667 %) > Variance from outliers : 78.9282 % Variance is severely inflated by > outliers > > > > The performance is amazingly better when using primitive doubles, even > though all operations are doing effectively the same thing: 20000 floating > point add operations. I assume that the overhead of allocating a new > vector2 object is responsible for the decline in performance. > > Did I use type hints correctly? Is there a way to keep the "x and y" > vector abstraction and still get something approaching primitive > performance? > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.