In other words, how much overhead is there in the transformation back
and forth, and therefore, about how much transient work does it take
before it becomes worth doing?
Excellent. I'm glad to hear that the core functions will use
transients so I don't have to make case-by-case decisions as to
whether to rewrite with transients things that are easily expressed
with those functions. (Although you didn't mention it, I assume assoc
with a sequence of changes would also be something that now uses
transients).
The transient function has no side effects, the persistent! function
does, thus the names.
Rich
Fully persistent modifications require full path copies, while
transient modifications copy nodes as needed, and once a copy has been
made, subsequent modifications affecting that node can be made in
place.
Rich
(defn make-transient-counter [init-val]
(let [acounter (transient [init-val])]
(fn [add-val] (assoc! acounter 0 (+ add-val (nth acounter 0))))))
Not persistent, so you can't hang onto interim values or alias
No, it wouldn't (currently), but there is no reason to do that. There
are the reference types for sharing things into unknown contexts, with
multithreaded semantics. Refs, atoms etc.
Rich
Mapping into vectors and similar ops are coming, although nothing like
vmap* would ever be exposed.
> And, of course:
> (defn vpmap [fun vect]
> (loop [out (vmap* #(future (fun %)) vect) i (dec (count vect))]
> (let [o2 (assoc! out i @(nth out i))]
> (if (zero? i)
> (persistent! o2)
> (recur o2 (dec i)))))
> Note that this last manipulates a transient vector in a single thread,
> though other threads (from the agent pool) calculate a bunch of futures that
> are stored in it. The transient vector of futures is generated using the
> vmap* from above, and then the futures are replaced with their values
> in-place by the loop in vpmap, before this persistentizes and returns that
> vector. The future-dereferencing loop works backwards from the end of the
> vector in case zero? is a quicker test than a general equality test. This is
> likely on hardware that implements an equality test as a subtraction
> followed by a zero test, because it eliminates the subtraction. On the other
> hand, hardware with a 1-cycle equality test of 32-bit ints is plausible, as
> is hardware that optimizes forward traversal of vectors, so I can't vouch
> for that being an optimization. The first loop has to go forward to conj up
> the output vector without reversing it, though.
>
There is already a very nice pvmap in the par branch that uses
ForkJoin. I strongly recommend against trying to write parallel ops
with transients - that's not what they are for.
What's nice about pvmap is that, just like transients, it also takes,
manipulates, and returns ordinary Clojure vectors (unlike the old
parallel lib which copied into and out of ForkJoin's ParallelArrays).
The goal is to make using the normal data structures as powerful as
possible, and not needing to switch to something else for performance.
Rich
Luc Préfontaine Armageddon was yesterday, today we have a real problem... |
(def transhashmap (transient {})
(assoc transhashmap "a" 1)
(assoc transhashmap "b" 2)
etc
Thank you, Christophe! I've been wanting to try those out.
I made changes to 3 lines of my Clojure program for the k-nucleotide
benchmark, which spends most of its time in a function tally-dna-subs-
with-len that creates a hash map counting the number of times that
each of a bunch of length k strings occurs in a long string. Source
here, if you're curious:
http://github.com/jafingerhut/clojure-benchmarks/blob/38e1f592ca3befe94a0674a5f5a43d952cd369b3/knuc/knucleotide.clj-7.clj
It went from about 19 minutes down to about 12 minutes. Excellent
improvement for a small change to the code. That brings it down to
about 7.7 times the run time of the Java version from the language
shootout web site.