Library for playing around with low level Clojure code for performance reasons given some assumptions. Inspired by Naked Performance (with Clojure) – Tommi Reiman.
Some of the code is based on implementations in metosin's projects. Credit in code.
This repo serves a dual purpose:
What makes it possible?
Plenty of Clojure's core functions are implemented to be generic (good)
and to accept a variable number of arguments (also very good). The
problem is that we pay for this in performance. Wherever we iterate over
a sequence of input arguments or dispatch on the class, we lose on
performance, especially when iterating on arguments and calling next,
more or rest repeatedly.
Plenty of these behaviors are just forms of flow-control, and like and
and or, other forms of flow control can too be statically analyzed,
under certain constraints, and replaced by faster code.
Add in your project.clj:
[bsless/clj-fast "0.0.6"]
(require '[clj-fast.core :as fast])
entry-at: used like find but doesn't dispatch and has inline
definition. Works for IPersistentMap.val-at: used like get but doesn't dispatch and has inline
definition. Works for IPersistentMap.fast-assoc: Used like assoc but doesn't take variable key-values,
only one pair and has inline definition. Works on Associative.fast-map-merge: Slightly faster version for merge, takes only 2
maps.rmerge!: merges a map into a transient map.(require '[clj-fast.inline :as inline])
Like regular core functions but sequence arguments must be written
explicitly for static analysis or defed in advance (i.e. resolve-able).
Examples:
(def ks [:a :b])
(inline/assoc m :a 1 :b 2)
(inline/fast-assoc m :a 1 :b 2)
(inline/get-in m ks)
(inline/get-in m [:c :d])
(inline/get-some-in m [:c :d])
(inline/assoc-in m [:c :d] foo)
(inline/update-in m [:c :d] inc)
(inline/select-keys m [:a :b :c])
(inline/merge m1 m2 m3)
(def assoc* (inline/memoize-c 3 assoc))
Besides being macros and requiring the keys to be statically defined, there are some other differences between the inline macros' and core functions' behavior:
select-keys: If a key is absent in the source map, it will contain
nil in the target map.fast-assoc: inlines in the same manner of assoc but uses
clj-fast.core/fast-assoc instead.fast-map-merge: inlines in the same manner of merge but uses
clj-fast.core/fast-map-merge instead (Metosin).get-some-in: Like get-in at the expense of working only on callables
(objects implementing clojure.lang.IFn).find-some-in: like get-some-in but returns a map-entry in the end,
like find.memoize* & memoize-c*: Alternative implementations for memoization
using a nested Clojure hash map and a nested Java concurrent hash map
respectively. Fall back to core/memoize for large arities. Due to
the cost of hashing objects in Clojure, it's recommended to use
memoize-c* for most use cases.(require '[clj-fast.collections.hash-map :as hm])
->hashmap: wraps HashMap's constructor.get: wraps method call for HashMap's get. Has inline definition.put: wraps method call for HashMap's put. Has inline definition.(require '[clj-fast.collections.concurrent-hash-map :as chm])
->concurrent-hash-map: constructor.concurrent-hash-map?: instance check.put!?: putIfAbsent.getget?: get if is a concurrent hash map.get-in?: like clojure core's get-in but for nested concurrent hash maps.put-in!: like clojure core's assoc-in but for nested concurrent hash maps.(require '[clj-fast.lens :as lens])
In typed functional programming, lenses are a generic way of getting and setting nested data structures (records).
In this context, the lens namespace implements the basic code structure
underlying Clojure's get-in, some->, assoc-in and update-in.
They can be used in macros to expand to real code provided an appropriate
1-depth get and/or put transformer, which takes arguments and returns
an expression.
For example, the get-some lens is used to define inline/get-some-in:
(defmacro get-some-in
  [m ks]
  (lens/get-some (fn [m k] `(~m ~k)) m ks))
Similarly, for assoc-in:
(defmacro assoc-in
  [m ks v]
  (lens/put
   (fn [m k v] `(c/assoc ~m ~k ~v))
   (fn [m k] `(c/get ~m ~k))
   m
   (u/simple-seq ks)
   v))
So be careful, these are not functional programming lenses, but metaprogramming lenses used for code generation.
See results.md for experiments' detailed benchmark results.
fast-assoc by Metosin.assoc which expands the "rest" args. (not tested)assoc-in which expands the keys sequence.fast-get by Metosin.fast-map-merge: Metosin's implementation. Uses kv-reduce to
fast-assoc all of one map into another.inline-merge: inlines core's merge reduction over a sequence of
maps with conj to a nested conj of all maps.inline-fast-map-merge: same but with Metosin's fast-map-merge.inline-tmerge: same but with Joinr's transient merge.inline-get-in: given that all keys are written as explicit arguments
and not a sequence, get-in can be expanded into a series of gets.inline-get-some-in: same as above, but maps can be invoked on the
keys. nil checks every iteration.memoize-n / memoize-c: Both implemented the same but on differing
underlying data structures, nested map in an atom and a nested concurrent
hash map, respectively. The main difference from core memoize is a
requirement that the arity to be memoized be specified at call time.
This allows inlining and better results.inline-assoc-in: same as inline-get-in but with assoc-in.inline-update-in: same as inline-assoc-in but with update-in.inline-select-keys: same case with get-in can be done with
select-keys.Copyright © 2019 ben.sless@gmail.com
This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.
Credit to Metosin wherever noted in the code.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs | 
| ← | Move to previous article | 
| → | Move to next article | 
| Ctrl+/ | Jump to the search field |