Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@rauhs
Last active June 30, 2017 20:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rauhs/771fb0fc8746958ca6f473914eeb1128 to your computer and use it in GitHub Desktop.
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) [])
@rauhs
Copy link
Author

rauhs commented Jun 19, 2017

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)]

@rauhs
Copy link
Author

rauhs commented Jun 19, 2017

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.

@rauhs
Copy link
Author

rauhs commented Jun 22, 2017

apply's args of my app are of type:

  • LazySeq: 500x
  • IndexedSeq: 2000x
  • js/Array: 170x
  • PersistentVector: 350x

@rauhs
Copy link
Author

rauhs commented Jun 22, 2017

Steps for optimization:

  1. Refactor core.cljc to use some helper functions to invoke the IFn proto: https://dev.clojure.org/jira/browse/CLJS-2127
  2. Implement faster apply-to
  3. Centralize cljc$lang$applyTo.

@rauhs
Copy link
Author

rauhs commented Jun 26, 2017

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.

@rauhs
Copy link
Author

rauhs commented Jun 26, 2017

Instead of using bounded count, can we just use the result of the spread-n "walk"?

@rauhs
Copy link
Author

rauhs commented Jun 26, 2017

The spread should return an an array with mfa number of elements where the last element is a n IndexedSeq with a given offset.

@rauhs
Copy link
Author

rauhs commented Jun 30, 2017

x.foo == null should be faster than a simple if x.foo

@rauhs
Copy link
Author

rauhs commented Jun 30, 2017

create two apply-to-simple: apply-to-multi-arity, apply-to-js-fn. Monomorphic fns should be faster.

@rauhs
Copy link
Author

rauhs commented Jun 30, 2017

bounded-count can call slow native-satisfies when passed nil.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment