Mercurial > rlm
diff src/rlm/function_utils.clj @ 0:78a630e650d2
initial import
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Tue, 18 Oct 2011 00:57:08 -0700 |
parents | |
children | 8565803376a4 c8e35134bf8e |
line wrap: on
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/rlm/function_utils.clj Tue Oct 18 00:57:08 2011 -0700 1.3 @@ -0,0 +1,185 @@ 1.4 +;; Various Operators on Pure Functions 1.5 +;; 1.6 +;; Open source Liscence and all that 1.7 +(ns 1.8 + rlm.function-utils 1.9 + "Collection of Operators on Pure Functions" 1.10 + {:author "Robert McIntyre"} 1.11 + (:use [clojure.contrib.profile])) 1.12 + 1.13 +(def void ::void) 1.14 + 1.15 +(comment 1.16 + 1.17 + "Please help me out here. I'm trying to make a higher order function that takes 1.18 + pure functions that are mathmaticaly the same but have different times and returns another 1.19 + function that runs them both and returns the resut of the one that finishes first." 1.20 + 1.21 + 1.22 + "here's a repl session: 1.23 + 1.24 +mobius> " (time (dotimes [_ 500]((mix-futures + +) 40 3 3 3 3 3))) " 1.25 +Elapsed time: 85.792995 msecs 1.26 +nil 1.27 +mobius> " (time (dotimes [_ 500](+ 40 3 3 3 3 3))) " 1.28 +Elapsed time: 6.956338 msecs 1.29 +nil 1.30 +mobius> " (time (dotimes [_ 500]((mix-threads + +) 40 3 3 3 3 3))) " 1.31 +Elapsed time: 706.227065 msecs 1.32 +nil 1.33 + 1.34 + 1.35 +mobius> " (defn fast-five [& args] 5) " 1.36 +#'mobius/fast-five 1.37 + 1.38 +mobius> " (defn slow-five [& args] (Thread/sleep 5000) (println "Oh YEAH!!!!") 5) " 1.39 +#'mobius/slow-five 1.40 + 1.41 +mobius> " (profile (time (dotimes [_ 5000] (thread-five)))) " 1.42 +\"Elapsed time: 12187.981587 msecs\" 1.43 + Name mean min max count sum 1.44 + create-atom 9621 2025 4433928 5000 48106830 1.45 +create-threads 5156 2724 2880268 5000 25783796 1.46 + kill-threads 91313 27866 11175718 5000 456567066 1.47 + loop 2124249 38482 18470781 5000 10621249899 1.48 + return 1242 838 5309 5000 6212626 1.49 + start-threads 252985 110348 12272835 5000 1264927953 1.50 +nil 1.51 + 1.52 +mobius> " (profile (time (dotimes [_ 5000] (future-five)))) " 1.53 +\"Elapsed time: 607.266671 msecs\" 1.54 + Name mean min max count sum 1.55 + create-atom 1472 1047 31708 5000 7363139 1.56 +create-futures 30539 2514 1330660 5000 152697998 1.57 + kill-threads 54158 2444 3938833 5000 270792897 1.58 + loop 81117 8800 6083059 5000 405587516 1.59 + return 1215 838 618782 5000 6078146 1.60 +nil 1.61 + 1.62 + 1.63 + 1.64 +What can I improve here, and why is the future version sooo much faster than the 1.65 +thread version? Is there a better way than using loop? 1.66 +" 1.67 + 1.68 +) 1.69 + 1.70 + 1.71 +(defn mix 1.72 + "Takes any number of mathematically equal functions with 1.73 + possibly different run-times and returns a function that 1.74 + runs each in a separate thread, returns the result from 1.75 + the first thread which finishes, and cancels the other threads." 1.76 + {:author "Robert McIntyre"} 1.77 + ([& functions] 1.78 + (fn [& args] 1.79 + (let [result (promise) 1.80 + futures (doall (for [fun functions] 1.81 + (future (deliver result (apply fun args))))) 1.82 + answer @result] 1.83 + (dorun (map future-cancel futures)) 1.84 + answer)))) 1.85 + 1.86 + 1.87 + 1.88 + 1.89 +(defn mix-threads 1.90 + " Takes any number of pure functions that take the same arguments and 1.91 + compute the same value and returns a function that runs each in a separate 1.92 + thread, returns the result from the first thread which finshes, and cancels 1.93 + the other threads. Explicitly uses nasty Threads. 1.94 + 1.95 + For example: 1.96 + (do 1.97 + (defn fun1 [] (Thread/sleep 5000) 5) 1.98 + (defn fun2 [] (Thread/sleep 700000) 5) 1.99 + (time ((mix fun1 fun2)))) 1.100 + 1.101 + Returns: 1.102 + | Elapsed time: 5000.66214 msecs 1.103 + 5" 1.104 + [& functions] 1.105 + (fn [& args] 1.106 + (let [result (prof :create-atom (atom void)) 1.107 + threads 1.108 + (prof :create-threads (map 1.109 + (fn [fun] 1.110 + (Thread. 1.111 + (fn [] 1.112 + (try (let [answer (apply fun args)] 1.113 + (reset! result answer)) 1.114 + (catch Exception _ nil))))) 1.115 + functions))] 1.116 + 1.117 + (prof :start-threads (dorun (map #(.start %) threads))) 1.118 + (prof :loop (loop [] 1.119 + (if (= (deref result) void) 1.120 + (recur) 1.121 + (do (prof :kill-threads (dorun (map #(.stop %) threads))) 1.122 + (prof :return (deref result))) 1.123 + )))))) 1.124 + 1.125 +(defmacro defmix 1.126 + " Defines a function from any number of pure functions that take the same 1.127 + arguments and compute the same value which: 1.128 + 1.129 + Runs each in a separate thread. 1.130 + Returns the result from the first thread which finshes. 1.131 + Cancels the other threads. 1.132 + 1.133 + Use this whenever you want to combine two pure functions that 1.134 + compute the same thing, but use different algorithms with different 1.135 + run times for various inputs. 1.136 + 1.137 + For example: 1.138 + (do 1.139 + (defn fun1 [] (Thread/sleep 5000) 5) 1.140 + (defn fun2 [] (Thread/sleep 700000) 5) 1.141 + (defmix fun3 \"combination of fun1 and fun2\" fun1 fun2) 1.142 + (time (fun3)) 1.143 + 1.144 + Returns: 1.145 + | Elapsed time: 5000.66214 msecs 1.146 + 5" 1.147 + 1.148 +{:arglists '([name doc-string? functions*])} 1.149 + 1.150 + [name & functions] 1.151 + (let [doc-string (if (string? (first functions)) (first functions) "") 1.152 + functions (if (string? (first functions)) (rest functions) functions) 1.153 + arglists (:arglists (meta (resolve (eval `(quote ~(first functions)))))) 1.154 + name (with-meta name (assoc (meta name) :arglists `(quote ~arglists) :doc doc-string))] 1.155 + `(def ~name (mix ~@functions)))) 1.156 + 1.157 + 1.158 +(defn runonce 1.159 + "Decorator. returns a function which will run only once. Inspired 1.160 + by Halloway's version from lancet." 1.161 + {:author "Robert McIntyre"} 1.162 + [function] 1.163 + (let [sentinel (Object.) 1.164 + result (atom sentinel)] 1.165 + (fn [& args] 1.166 + (locking sentinel 1.167 + (if (= @result sentinel) 1.168 + (reset! result (apply function args)) 1.169 + @result))))) 1.170 + 1.171 + 1.172 + 1.173 +;I'm thinking this will be the docstring for mix eventually. 1.174 + 1.175 + ;; " Takes any number of pure functions that take the same arguments and 1.176 + ;; compute the same value and returns a function that runs each in a separate 1.177 + ;; thread, returns the result from the first thread which finshes, and cancels 1.178 + ;; the other threads. 1.179 + 1.180 + ;; For example: 1.181 + ;; (do 1.182 + ;; (defn fun1 [] (Thread/sleep 5000) 5) 1.183 + ;; (defn fun2 [] (Thread/sleep 700000) 5) 1.184 + ;; (time ((mix fun1 fun2)))) 1.185 + 1.186 + ;; Returns: 1.187 + ;; | Elapsed time: 5000.66214 msecs 1.188 + ;; 5"