Fork me on GitHub
#clojure-dev
<
2017-10-23
>
danielcompton00:10:47

@ikitommi with that ticket in particular, microbenchmarks on their own will be misleading, you will need to test across many differently sized vectors to get a result that will reflect real world use

tbaldridge01:10:05

@ikitommi what does your change do? Sometimes optimizations like this are simple and a clear win (Rich put one in for vector destructuring recently) while others can be really hard to debug due to callsites and how they work in the JVM

ikitommi05:10:30

Thanks for the tips. 1) for keyword destructuring, change from (get m key) to (key m) => enables fast access for records and is still nil safe (50ns -> 36ns for Records, no change for maps) 2) separate the generated inlined empty-sequence-check into a separate function, using a protocol dispatch (no MI here, should be fast all the time), takes 20ns of from both cases.

(defprotocol IntoMap
  (into-map [x]))

(extend-protocol IntoMap
  ISeq
  (into-map [m]
    (clojure.lang.PersistentHashMap/create (clojure.core/seq m)))
  Object
  (into-map [m] m))

ikitommi05:10:51

jmh-clojure looks nice.

bronsa10:10:39

that has nothing to do with this case, ISeq is more specialized than Object

ikitommi11:10:59

ok, thanks.

slipset10:10:36

On optimizations, it seems (from CLJ-1789) that using reduce instead of loop/recur with rest is faster. Based on this, I tried out implementing some in terms of reduce, as such:

slipset10:10:10

(defn some [p coll]
  (reduce (fn [a v] (when-let [r (p v)] (reduced r))) nil coll))

slipset10:10:44

Preliminary tests show that it’s faster for vectors, but clearly slower for maps.

slipset10:10:01

Would a ticket for something like this be of interest?

alexmiller19:10:19

sure, but given that it’s faster in some cases and slower in others, it does not seem like an unambiguous win?

slipset10:10:32

Same goes for every?

slipset10:10:37

(defn my-every? [p coll]
  (reduce (fn [a v] (if-not (p v) (reduced false) true)) true coll))

slipset10:10:33

clj.user> (def r (range 10))
;; => #'clj.user/r
clj.user> (criterium.core/quick-bench (every? identity r))
Evaluation count : 4809738 in 6 samples of 801623 calls.
             Execution time mean : 127.011808 ns
    Execution time std-deviation : 5.742864 ns
   Execution time lower quantile : 121.973369 ns ( 2.5%)
   Execution time upper quantile : 133.507575 ns (97.5%)
                   Overhead used : 1.438648 ns
;; => nil
clj.user> (criterium.core/quick-bench (my-every? identity r))
Evaluation count : 8526336 in 6 samples of 1421056 calls.
             Execution time mean : 72.917310 ns
    Execution time std-deviation : 2.263637 ns
   Execution time lower quantile : 70.634822 ns ( 2.5%)
   Execution time upper quantile : 75.335391 ns (97.5%)
                   Overhead used : 1.438648 ns

ghadi15:10:58

@slipset some of the challenges are do to the way protocols are bootstrapped in core. Reduce isn't available until a bunch of things are loaded first

slipset15:10:49

But could you move some/every? until after reduce and maybe declare them if they’re used before reduce?

alexmiller19:10:43

playing core bootstrap Jenga is a fun way to pass the time :)

alexmiller19:10:20

the technique taken in some cases is to start with a slow implementation, then reimplement with a fast one farther down in core

alexmiller19:10:32

after the needed things are available

dpsutton19:10:58

as in there would be more than one version of a function? And as more tools get defined you can change the implementation?

noisesmith19:10:17

that’s what happens with reduce and reduce1 right?

alexmiller19:10:58

well in that case there are two different functions

alexmiller19:10:18

I was talking about the case where we literally reimplement the function

alexmiller19:10:42

I can’t remember which one off the top of my head

alexmiller19:10:24

maybe those are macros

bronsa19:10:40

yeah some macro redefined for destructuring

alexmiller19:10:45

stuff like let is defined early (before destructuring) then redefined again later

dpsutton20:10:19

so yall can use let in core before before defining all of the hairy destructuring parts?

alexmiller20:10:46

yeah, just search for “redefine” in clojure/core.clj and you can find a few things like that

dpsutton20:10:53

will do. thanks

cfleming22:10:12

I asked this question a while back, but made the mistake of asking during the conj when everyone was busy. I didn’t get an unambiguous answer so I’ll try again: when direct linking is enabled, calls frequently appear twice in the stacktrace, one being the var invocation and one being the static method it delegates to. They frequently have different source line numbers, and the static one generally seems to be more accurate. Is that the one I should use for linking to that call?

alexmiller22:10:00

I think so - I don't actually understand why they end up being tracked to different lines.

cfleming23:10:29

No, me either.

alexmiller23:10:36

Assume it's something off in how the source line debug info is calculated in the two methods