clojure-dev

Issues: https://clojure.atlassian.net/browse/CLJ | Guide: https://insideclojure.org/2015/05/01/contributing-clojure/
serioga 2021-02-13T19:43:19.102400Z

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 ...)))?

ghadi 2021-02-13T19:46:48.103900Z

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)

seancorfield 2021-02-13T19:48:19.104500Z

That sounds very familiar -- I swear that's come up recently... gimme a sec...

seancorfield 2021-02-13T19:49:31.105300Z

(no Jira ticket -- Alex is looking for a compelling example)

serioga 2021-02-13T19:49:34.105400Z

Thank you @seancorfield

serioga 2021-02-13T19:50:46.105500Z

benchmarks give me 2-3× difference :-)

ghadi 2021-02-13T19:51:17.105700Z

nice

borkdude 2021-02-13T19:52:24.106400Z

@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

serioga 2021-02-13T19:53:14.107Z

@borkdude we about ->Foo here 🙂

borkdude 2021-02-13T19:53:24.107200Z

does it matter how the record is produced?

borkdude 2021-02-13T19:53:57.107500Z

barring reflection issues

borkdude 2021-02-13T19:55:34.107800Z

(example updated)

serioga 2021-02-13T20:00:44.108500Z

(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"

borkdude 2021-02-13T20:01:43.109Z

The first usage just shows that reflection in a loop is slow. The other examples need more loops to be able to compare better.

ghadi 2021-02-13T20:03:16.110200Z

hmm I'm reproducing your result @borkdude

serioga 2021-02-13T20:03:46.111100Z

criterium gives 1-2 ns for field access and 6 ns for keyword

ghadi 2021-02-13T20:03:50.111400Z

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"

ghadi 2021-02-13T20:04:22.112Z

this is what I expect if a record has an x field

borkdude 2021-02-13T20:04:55.112600Z

@ghadi yes, but direct interop is still x10 faster in my result

ghadi 2021-02-13T20:05:01.112900Z

yeah

borkdude 2021-02-13T20:05:25.113800Z

which makes sense probably since invoking a keyword is invoking a function which is another indirection

ghadi 2021-02-13T20:05:39.114Z

no, there is a special case in the compiler

ghadi 2021-02-13T20:05:50.114500Z

when keyword appears first

borkdude 2021-02-13T20:05:53.114700Z

ah

ghadi 2021-02-13T20:07:23.115800Z

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"

bronsa 2021-02-13T20:07:53.116600Z

it makes sense that the kw invoke path is still slower than direct invocation

serioga 2021-02-13T20:07:54.117Z

yes, keyword access from map is slower than from record at least in repl benchmarks

bronsa 2021-02-13T20:08:12.117600Z

there's quite a lot more bytecode involved and branching

ghadi 2021-02-13T20:08:59.118700Z

yeah, I have an invokedynamic variant of this keyword access optimization that has less bytecode

ghadi 2021-02-13T20:09:16.119200Z

anyways, it is unlikely going to dominate in any benchmarks, unless of course it reflects