-
-
Save rauhs/771fb0fc8746958ca6f473914eeb1128 to your computer and use it in GitHub Desktop.
;; No ticket yet. Idea: | |
(defn spread-n | |
"arr will be mutated!! | |
Puts n more items into arr and the rest of xs as the last element. | |
Returns the array arr." | |
[arr n xs] | |
(loop [s (seq xs) | |
i (dec n)] | |
(if (pos? i) | |
(do | |
(.push arr (-first s)) | |
(recur (next s) (dec i))) | |
(do | |
(.push arr s) | |
arr)))) | |
(spread-n #js[0] 2 [1 2 3]) | |
(defn my-apply | |
([f x args] | |
(if (.-cljs$lang$applyTo f) | |
(let [fixed-arity (.-cljs$lang$maxFixedArity f) | |
bc (inc (bounded-count fixed-arity args))] | |
(if (<= bc fixed-arity) | |
;; TODO: Optimize apply-to similarlyto apply-to-simple: | |
(apply-to f bc (list* x args)) | |
(.apply (.-cljs$core$IFn$_invoke$arity$variadic f) | |
nil | |
(spread-n #js[x] fixed-arity args)))) | |
(apply-to-simple f x (seq args))))) | |
(my-apply (fn [a b & c] [a b c]) | |
1 [1 2]) | |
(defn pv-to-array | |
"Returns the (possibly internal) array for the given PersistentVector" | |
[v] | |
(let [cnt (.-cnt v) | |
arr (unchecked-array-for v 0)] | |
(if (< 32 cnt) | |
(let [arr (aclone arr)] | |
(loop [i 32] | |
(when (< i cnt) | |
(let [new-arr (unchecked-array-for v i) | |
len (alength new-arr)] | |
(loop [j 0] | |
(when (< j len) | |
(.push arr (aget new-arr j)) | |
(recur (inc j)))) | |
(recur (+ i len))))) | |
arr) | |
arr))) | |
;; DOnt use the above, instead use (seq [some-vec]). It's very efficient. | |
;; Basically, write a sub-seq for a few types. |
;; LazySeq: 500x | |
;; => Walk first/next | |
;; IndexedSeq: 2000x | |
;; js/Array: 170x | |
;; => Very fast & easy | |
;; PersistentVector: 350x | |
;; => | |
(defn pv-subseq | |
"" | |
[pv start] | |
;; Invariant: start is not negative! | |
(let [cnt (.-cnt pv)] | |
(cond | |
(not (pos? (- cnt start))) nil | |
(<= cnt 32) (IndexedSeq. (.-tail pv) start nil) | |
:else (seq (subvec pv start))))) | |
#_(pv-subseq (vec (range 40)) 30) | |
;; Get MFA items out of a seq, + the rest of the seq (nil'd) | |
;; First do the generic fallback: | |
;; | |
;; Should we do apply-to-varidic be a JS array, then just | |
;; pluck out the values with args[0], args[1] + IndexedSeq(args,2,null) | |
;; Candidates: | |
#js[+ - * / < > = not=] | |
(defn spread-n | |
"Makes " | |
[arr n args] | |
(if (zero? n) | |
args | |
(cond | |
(instance? IndexedSeq args) | |
(let [arg-arr (.-arr args) | |
offset (.-i args)] | |
;; We have 3 cases: | |
;; 1. Push more items into arr + a new IndexedSeq with offset | |
;; 2. It's already good | |
;; 3. Pull items from arr into args (use cons) | |
())))) | |
(defn- maybe-count | |
"Counts in constant time or nil if that's not possible" | |
[coll] | |
(cond | |
(implements? ICounted coll) (-count ^not-native coll) | |
(array? coll) (alength coll) | |
:else nil)) | |
#_(maybe-count #js[1]) | |
#_(maybe-count (prim-seq #js[0 1 2])) | |
#_(maybe-count (lazy-seq [0 1 2])) | |
;; Current applyTo uses first/next! | |
;; Cases: | |
;; 1. We know count & can decide mfa or not => FAST | |
;; a) Call the proper exact arity, FAST by just dumping it all into an array | |
;; REUSE | |
;; b) Call the the variadic arity, FAST by spread-n | |
;; Jump to the arity and pluck out of array | |
;; 2. We dont know count, then | |
;; Walk first/next until we either: | |
;; a) Get nil as next, call the IFn | |
;; b) Passed MFA. Call the variadic fn. | |
;; What if we have a whole in the arities, like [a] [a b c & d] ?? | |
;; And we pass 2 args? Go to the dispatcher and let it error? | |
;; But overall: Easy to code | |
(defn apply-to-variadic-walker | |
"Internal! DO NOT USE! | |
Applies to args to f using first/next" | |
[f args]) | |
(defn apply-to-variadic | |
"Internal! DO NOT USE! | |
Invokes the variadic function" | |
;; We need the first MFA items out of args then call | |
;; the | |
[f mfa args] | |
(case mfa | |
0 (invoke-variadic* f args) | |
1 (invoke-variadic* f (aget args 0) (aget args 1)) | |
2 (invoke-variadic* f (aget args 0) (aget args 1) (aget args 2)) | |
;; Fall back with .apply at the end! | |
)) | |
(defn into-array-fast | |
[coll] | |
(into-array coll)) | |
(set! *unchecked-if* true) | |
(defn- apply-to-simple-new | |
[f arr args] | |
(let [IS? (instance? IndexedSeq args) | |
off (if IS? (.-i args) 0) | |
arr (if IS? (.-arr args) (into-array args))] | |
(case (alength arr) | |
0 (if (invokable? f 0) (invoke* f) (.call f f)) | |
1 (if (invokable? f 1) (invoke* f (aget arr (+ 0 off))) | |
(.call f f (aget arr (+ 0 off))))))) | |
(defn apply-new | |
"Applies fn f to the argument list formed by prepending intervening arguments to args." | |
([f args] | |
(if (invokable? f "variadic") | |
(let [mfa (.-cljs$lang$maxFixedArity f) | |
bc (bounded-count (inc mfa) args)] | |
(if (<= bc mfa) | |
(apply-to f bc args) | |
(apply-to-variadic f mfa args))) | |
;; Need to handle offset | |
(apply-to-simple-new f #js[] args)))) | |
(set! *unchecked-if* false) | |
;; Test: | |
;; 1. We need to handle nil: | |
(apply + 1 2 nil) | |
(apply (fn [& a] a) []) |
Actually, should avoid the javascript .apply
by invoking directly for up to 10(?) args of variadic. Then fallback to an .apply
call on the variadic function.
apply
's args of my app are of type:
- LazySeq: 500x
- IndexedSeq: 2000x
- js/Array: 170x
- PersistentVector: 350x
Steps for optimization:
- Refactor core.cljc to use some helper functions to invoke the
IFn
proto: https://dev.clojure.org/jira/browse/CLJS-2127 - Implement faster
apply-to
- Centralize
cljc$lang$applyTo
.
Dispatchers for CLJS functions are only needed when doing variadic invokes, thus all other dispatching code can be removed.
Any .call
and .apply
for CLJS data structures isn't needed anymore. Can be removed.
Instead of using bounded count, can we just use the result of the spread-n
"walk"?
The spread should return an an array with mfa number of elements where the last element is a n IndexedSeq
with a given offset.
x.foo == null
should be faster than a simple if x.foo
create two apply-to-simple
: apply-to-multi-arity
, apply-to-js-fn
. Monomorphic fns should be faster.
bounded-count can call slow native-satisfies
when passed nil
.
Probably also needs to handle the opposite case. Ie
(spread-n #js[a b c] -1 [d e f])
->#js[a b (list* c d e f)]