Is there an issue about that the defrecord's function ->Record
does not have :tag meta, so you need to use type hinting to access record fields using dot notation (like (.field ^Record (->Record ...))
)?
not sure, but FYI accesses to a record's fields have an inline optimization, so you can do (:foo r)
and it should be just as fast as a type-hinted (.-foo ^Record r)
That sounds very familiar -- I swear that's come up recently... gimme a sec...
https://ask.clojure.org/index.php/9168/defrecord-could-type-hint-the-record-constructor-functions
(no Jira ticket -- Alex is looking for a compelling example)
Thank you @seancorfield
benchmarks give me 2-3× difference :-)
nice
@ghadi I am measuring something wrong here?
user=> (defrecord Foo [x])
user.Foo
user=> (let [^Foo r (->Foo 1)] (time (dotimes [i 1000000000] (.-x r))))
"Elapsed time: 260.812401 msecs"
nil
user=> (let [^Foo r (->Foo 1)] (time (dotimes [i 1000000000] (:x r))))
"Elapsed time: 2370.017491 msecs"
nil
@borkdude we about ->Foo
here 🙂
does it matter how the record is produced?
barring reflection issues
(example updated)
(do
(let [x (->Foo 1)] (time (dotimes [_ 1000000] (.a x))))
(let [x (->Foo 1)] (time (dotimes [_ 1000000] (.a ^Foo x))))
(let [x (Foo. 1)] (time (dotimes [_ 1000000] (.a x))))
(let [x (Foo. 1)] (time (dotimes [_ 1000000] (:a x)))))
"Elapsed time: 1895.1031 msecs"
"Elapsed time: 3.8925 msecs"
"Elapsed time: 4.4709 msecs"
"Elapsed time: 11.5897 msecs"
The first usage just shows that reflection in a loop is slow. The other examples need more loops to be able to compare better.
hmm I'm reproducing your result @borkdude
criterium gives 1-2 ns for field access and 6 ns for keyword
user=> (defn kwaccess [v] (:x v))
#'user/kwaccess
user=> (defn getaccess [v] (get v :x))
#'user/getaccess
user=> (let [r (->Foo 1)] (time (dotimes [i 1000000000] (kwaccess r))))
"Elapsed time: 2810.895121 msecs"
nil
user=> (let [r (->Foo 1)] (time (dotimes [i 1000000000] (getaccess r))))
"Elapsed time: 8820.251502 msecs"
this is what I expect if a record has an x
field
@ghadi yes, but direct interop is still x10 faster in my result
yeah
which makes sense probably since invoking a keyword is invoking a function which is another indirection
no, there is a special case in the compiler
when keyword appears first
ah
user=> (let [^Foo r (->Foo 1)] (time (dotimes [i 1000000000] (.-x r))))
"Elapsed time: 565.111442 msecs"
nil
user=> (let [^Foo r (->Foo 1)] (time (dotimes [i 1000000000] (:x r))))
"Elapsed time: 2362.38707 msecs"
nil
user=> (let [^Foo r (->Foo 1)] (time (dotimes [i 1000000000] (get r :x))))
"Elapsed time: 12014.947945 msecs"
it makes sense that the kw invoke path is still slower than direct invocation
yes, keyword access from map is slower than from record at least in repl benchmarks
there's quite a lot more bytecode involved and branching
yeah, I have an invokedynamic variant of this keyword access optimization that has less bytecode
anyways, it is unlikely going to dominate in any benchmarks, unless of course it reflects