fn-in
library performance summary
Version 5 introduces type dispatch with Clojure protocols, bringing improved performance compared to version 4's multimethod type dispatch. Across a wide span of collection types and collection sizes, all eight of the public functions are significantly faster in at least some cases, while none regress. Upgrading to version 5 therefore provides improved performance for general uses.
To ensure that any implementation change gives general performance improvement and not merely for one special case, we use Fastester to test a broad span of arguments. That involves running Criterium benchmarks for various sizes of vectors, maps, sequences, and lists for each of
fn-in
's eight functions.
For example, to test the performance of associating a new value into a flat (i.e., un-nested) vector, we do a lookup in
vec-of-n-rand-ints
, whose ten-element entry looks like this.
(vec-of-n-rand-ints 10) ;; => [68 71 2 89 4 19 35 4 84 37]
This provides the benchmark with a pre-made, one-dimensional series of random integers. We pre-compute the collections so that the benchmark does not measure the time it takes to construct the argument collection.
In our example, a single benchmark run takes multiple measurements of the time to associate a new value to the final element. Like this.
(assoc* (vec-of-n-rand-ints 10) (dec 10) :benchmark-sentinel)
;; => [68 71 2 89 4 19 35 4 84 :benchmark-sentinel]
assoc*
replaces the 37
at the end of the test vector with :benchmark-sentinel
in about 151 nanoseconds.
The benchmarks for the 'plain' functions (i.e., get*
, assoc*
, update*
, and dissoc*
) cover hashmaps,
lists, sequences, and vectors, each containing up to one million elements. In total, over fifty benchmarks per function.
The -in
function variants are benchmarked against nested versions of four collection types. For example, to test updating a value buried
in a nested vector, we do a lookup in nested-vec
, whose third entry looks like this.
(nested-vec 3)
;; => [[[0 1 2]
;; [3 4 5]
;; [6 7 8]]
;; [[9 10 11]
;; [12 13 14]
;; [15 16 17]]
;; [[18 19 20]
;; [21 22 23]
;; [24 25 26]]]
This supplies the benchmark with a pre-made, triply-nested series of vectors containing a range of integers.
A single benchmark run takes multiple measurements of the time to increment the last element of the last nested vector. Like this.
(update-in* (nested-vec 3) [2 2 2] inc)
;; => [[[0 1 2]
;; [3 4 5]
;; [6 7 8]]
;; [[9 10 11]
;; [12 13 14]
;; [15 16 17]]
;; [[18 19 20]
;; [21 22 23]
;; [24 25 27]]]
update-in*
dives into the nested collection and increments the final 26
to a 27
in about 576 nanoseconds.
For each of the -in
function variants (i.e., get-in*
, assoc-in*
, update-in*
, and
dissoc-in*
), we run over fifty benchmarks, involving deeply-nested (up to six levels) hashmaps, lists, sequences, and vectors, whose sizes
span up to one-hundred thousand elements.
get*
get-in*
assoc*
assoc-in*
update*
update-in*
dissoc*
dissoc-in*