From 7ab60216304a347016d292ea6fa672a20c7a8420 Mon Sep 17 00:00:00 2001 From: Ben Sless Date: Mon, 30 Aug 2021 10:10:32 +0300 Subject: [PATCH 1/7] Add faster keywordize-keys implementation for clj --- modules/reitit-core/src/reitit/coercion.cljc | 2 +- modules/reitit-core/src/reitit/walk.clj | 52 ++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 modules/reitit-core/src/reitit/walk.clj diff --git a/modules/reitit-core/src/reitit/coercion.cljc b/modules/reitit-core/src/reitit/coercion.cljc index 8dd08d83..bc3dc6ca 100644 --- a/modules/reitit-core/src/reitit/coercion.cljc +++ b/modules/reitit-core/src/reitit/coercion.cljc @@ -1,5 +1,5 @@ (ns reitit.coercion - (:require [clojure.walk :as walk] + (:require [#?(:clj reitit.walk :cljs clojure.walk) :as walk] [reitit.impl :as impl]) #?(:clj (:import (java.io Writer)))) diff --git a/modules/reitit-core/src/reitit/walk.clj b/modules/reitit-core/src/reitit/walk.clj new file mode 100644 index 00000000..b8b832fd --- /dev/null +++ b/modules/reitit-core/src/reitit/walk.clj @@ -0,0 +1,52 @@ +(ns ^:no-doc reitit.walk) + +(defprotocol Walkable + (-walk [coll f])) + +(extend-protocol Walkable + nil + (-walk [_ _] nil) + Object + (-walk [x _] x) + clojure.lang.IMapEntry + (-walk [e f] (clojure.lang.MapEntry. (f (.key e)) (f (.val e)))) + clojure.lang.ISeq + (-walk [coll f] (map f coll)) + clojure.lang.PersistentList + (-walk [coll f] (apply list (map f coll))) + clojure.lang.PersistentList$EmptyList + (-walk [x _] x) + clojure.lang.IRecord + (-walk [r f] (reduce (fn [r x] (conj r (f x))) r r))) + +(defn- -walk-default + [coll f] + (into (empty coll) (map f) coll)) + +(doseq [type [clojure.lang.PersistentArrayMap + clojure.lang.PersistentHashMap + clojure.lang.PersistentHashSet + clojure.lang.PersistentVector + clojure.lang.PersistentQueue + clojure.lang.PersistentStructMap + clojure.lang.PersistentTreeMap + clojure.lang.PersistentTreeSet]] + (extend type Walkable {:-walk -walk-default})) + +(defn walk + [inner outer form] + (outer (-walk form inner))) + +(defn postwalk [f form] (walk (partial postwalk f) f form)) + +(defn- keywordize + [m] + (persistent! + (reduce-kv + (fn [m k v] (if (string? k) (assoc! m (keyword k) v) m)) + (transient {}) + m))) + +(defn keywordize-keys + [m] + (postwalk (fn [x] (if (map? x) (keywordize m) x)) m)) From dcb7258caf8ca5c4e5fa4c2c9c40c3d2c587614d Mon Sep 17 00:00:00 2001 From: Ben Sless Date: Sun, 25 Aug 2024 18:50:43 +0300 Subject: [PATCH 2/7] Tailor keywordize implementation to concrete types Even faster --- modules/reitit-core/src/reitit/walk.clj | 77 ++++++++++++------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/modules/reitit-core/src/reitit/walk.clj b/modules/reitit-core/src/reitit/walk.clj index b8b832fd..77369579 100644 --- a/modules/reitit-core/src/reitit/walk.clj +++ b/modules/reitit-core/src/reitit/walk.clj @@ -1,52 +1,51 @@ (ns ^:no-doc reitit.walk) -(defprotocol Walkable - (-walk [coll f])) +(defprotocol IKeywordize + (-keywordize [coll])) -(extend-protocol Walkable - nil - (-walk [_ _] nil) - Object - (-walk [x _] x) - clojure.lang.IMapEntry - (-walk [e f] (clojure.lang.MapEntry. (f (.key e)) (f (.val e)))) - clojure.lang.ISeq - (-walk [coll f] (map f coll)) - clojure.lang.PersistentList - (-walk [coll f] (apply list (map f coll))) - clojure.lang.PersistentList$EmptyList - (-walk [x _] x) - clojure.lang.IRecord - (-walk [r f] (reduce (fn [r x] (conj r (f x))) r r))) +(defn- -keywordize-map + [m] + (persistent! + (reduce-kv + (fn [m k v] + (if (string? k) + (assoc! m (keyword k) (-keywordize v)) + (assoc! m (-keywordize k) (-keywordize v)))) + (transient (empty m)) + m))) -(defn- -walk-default - [coll f] - (into (empty coll) (map f) coll)) +(defn- -keywordize-default + [coll] + (into (empty coll) (map -keywordize) coll)) -(doseq [type [clojure.lang.PersistentArrayMap - clojure.lang.PersistentHashMap - clojure.lang.PersistentHashSet +(doseq [type [clojure.lang.PersistentHashSet clojure.lang.PersistentVector clojure.lang.PersistentQueue clojure.lang.PersistentStructMap clojure.lang.PersistentTreeMap clojure.lang.PersistentTreeSet]] - (extend type Walkable {:-walk -walk-default})) + (extend type IKeywordize {:-keywordize -keywordize-default})) -(defn walk - [inner outer form] - (outer (-walk form inner))) +(doseq [type [clojure.lang.PersistentArrayMap + clojure.lang.PersistentHashMap]] + (extend type IKeywordize {:-keywordize -keywordize-map})) -(defn postwalk [f form] (walk (partial postwalk f) f form)) +(extend-protocol IKeywordize + nil + (-keywordize [_] nil) + Object + (-keywordize [x] x) + clojure.lang.MapEntry + (-keywordize [e] (clojure.lang.MapEntry/create + (-keywordize (.key e)) + (-keywordize (.val e)))) + clojure.lang.ISeq + (-keywordize [coll] (map -keywordize coll)) + clojure.lang.PersistentList + (-keywordize [coll] (apply list (map -keywordize coll))) + clojure.lang.PersistentList$EmptyList + (-keywordize [x] x) + clojure.lang.IRecord + (-keywordize [r] (reduce (fn [r x] (conj r (-keywordize x))) r r))) -(defn- keywordize - [m] - (persistent! - (reduce-kv - (fn [m k v] (if (string? k) (assoc! m (keyword k) v) m)) - (transient {}) - m))) - -(defn keywordize-keys - [m] - (postwalk (fn [x] (if (map? x) (keywordize m) x)) m)) +(defn keywordize-keys [m] (-keywordize m)) From 59642e51f166dff69bc882112ed9d9364cc8976f Mon Sep 17 00:00:00 2001 From: Ben Sless Date: Sun, 25 Aug 2024 18:54:55 +0300 Subject: [PATCH 3/7] Decrease code size and eliminate an allocation --- modules/reitit-core/src/reitit/walk.clj | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/reitit-core/src/reitit/walk.clj b/modules/reitit-core/src/reitit/walk.clj index 77369579..2def25d8 100644 --- a/modules/reitit-core/src/reitit/walk.clj +++ b/modules/reitit-core/src/reitit/walk.clj @@ -3,16 +3,13 @@ (defprotocol IKeywordize (-keywordize [coll])) +(defn- keywordize-kv + [m k v] + (assoc! m (if (string? k) (keyword k) (-keywordize k)) (-keywordize v))) + (defn- -keywordize-map [m] - (persistent! - (reduce-kv - (fn [m k v] - (if (string? k) - (assoc! m (keyword k) (-keywordize v)) - (assoc! m (-keywordize k) (-keywordize v)))) - (transient (empty m)) - m))) + (persistent! (reduce-kv keywordize-kv (transient (empty m)) m))) (defn- -keywordize-default [coll] From 61783e4c81587d4887a9b3fb55c340bfa2de521d Mon Sep 17 00:00:00 2001 From: Ben Sless Date: Sun, 25 Aug 2024 18:56:30 +0300 Subject: [PATCH 4/7] Statically def transducer Eliminates allocation and friendlier to JIT --- modules/reitit-core/src/reitit/walk.clj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/reitit-core/src/reitit/walk.clj b/modules/reitit-core/src/reitit/walk.clj index 2def25d8..132153ab 100644 --- a/modules/reitit-core/src/reitit/walk.clj +++ b/modules/reitit-core/src/reitit/walk.clj @@ -11,9 +11,11 @@ [m] (persistent! (reduce-kv keywordize-kv (transient (empty m)) m))) +(def ^:private keywordize-xf (map -keywordize)) + (defn- -keywordize-default [coll] - (into (empty coll) (map -keywordize) coll)) + (into (empty coll) keywordize-xf coll)) (doseq [type [clojure.lang.PersistentHashSet clojure.lang.PersistentVector From 7dfc0e5fca6f2064b3bf09afbc574d15f3d799ac Mon Sep 17 00:00:00 2001 From: Ben Sless Date: Sun, 25 Aug 2024 19:19:26 +0300 Subject: [PATCH 5/7] Fix dynamism --- modules/reitit-core/src/reitit/walk.clj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/reitit-core/src/reitit/walk.clj b/modules/reitit-core/src/reitit/walk.clj index 132153ab..f28ead5f 100644 --- a/modules/reitit-core/src/reitit/walk.clj +++ b/modules/reitit-core/src/reitit/walk.clj @@ -3,6 +3,8 @@ (defprotocol IKeywordize (-keywordize [coll])) +(defn keywordize-keys [m] (-keywordize m)) + (defn- keywordize-kv [m k v] (assoc! m (if (string? k) (keyword k) (-keywordize k)) (-keywordize v))) @@ -11,7 +13,7 @@ [m] (persistent! (reduce-kv keywordize-kv (transient (empty m)) m))) -(def ^:private keywordize-xf (map -keywordize)) +(def ^:private keywordize-xf (map keywordize-keys)) (defn- -keywordize-default [coll] @@ -30,10 +32,10 @@ (extend type IKeywordize {:-keywordize -keywordize-map})) (extend-protocol IKeywordize - nil - (-keywordize [_] nil) Object (-keywordize [x] x) + nil + (-keywordize [_] nil) clojure.lang.MapEntry (-keywordize [e] (clojure.lang.MapEntry/create (-keywordize (.key e)) @@ -46,5 +48,3 @@ (-keywordize [x] x) clojure.lang.IRecord (-keywordize [r] (reduce (fn [r x] (conj r (-keywordize x))) r r))) - -(defn keywordize-keys [m] (-keywordize m)) From 4eab67a8dbb6b403dbe92c4bbdc2604dbb391baf Mon Sep 17 00:00:00 2001 From: Ben Sless Date: Sun, 25 Aug 2024 19:25:54 +0300 Subject: [PATCH 6/7] reduce-kv over treemap --- modules/reitit-core/src/reitit/walk.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/reitit-core/src/reitit/walk.clj b/modules/reitit-core/src/reitit/walk.clj index f28ead5f..8e6a59ea 100644 --- a/modules/reitit-core/src/reitit/walk.clj +++ b/modules/reitit-core/src/reitit/walk.clj @@ -23,12 +23,12 @@ clojure.lang.PersistentVector clojure.lang.PersistentQueue clojure.lang.PersistentStructMap - clojure.lang.PersistentTreeMap clojure.lang.PersistentTreeSet]] (extend type IKeywordize {:-keywordize -keywordize-default})) (doseq [type [clojure.lang.PersistentArrayMap - clojure.lang.PersistentHashMap]] + clojure.lang.PersistentHashMap + clojure.lang.PersistentTreeMap]] (extend type IKeywordize {:-keywordize -keywordize-map})) (extend-protocol IKeywordize From 78cc54d3a88a12a0495aab885813f246cbdc50c3 Mon Sep 17 00:00:00 2001 From: Ben Sless Date: Mon, 26 Aug 2024 17:07:51 +0300 Subject: [PATCH 7/7] Add generative test for new keywordize --- test/clj/reitit/walk_test.clj | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test/clj/reitit/walk_test.clj diff --git a/test/clj/reitit/walk_test.clj b/test/clj/reitit/walk_test.clj new file mode 100644 index 00000000..2b5c42fd --- /dev/null +++ b/test/clj/reitit/walk_test.clj @@ -0,0 +1,13 @@ +(ns reitit.walk-test + (:require + [clojure.test.check.clojure-test :refer [defspec]] + [clojure.test.check.properties :as prop] + [clojure.test.check.generators :as gen] + [clojure.walk :as walk] + [reitit.walk :as sut])) + +(defspec keywordize=walk-keywordize + 10000 + (prop/for-all [v gen/any-equatable] + (= (sut/keywordize-keys v) + (walk/keywordize-keys v))))