From e55d4ccf8f961d38b3ad46ecb4615b00e31d72ea Mon Sep 17 00:00:00 2001 From: Yevgeni Tsodikov Date: Thu, 1 Dec 2022 17:04:47 +0200 Subject: [PATCH] Optimize `aerospike-record/record->map` (#61) * Optimize `aerospike-record/record->map` 1. Avoid costly `(into {} ..)`, we can use the original `(.bins record)`. 2. No need for the `keys` function, since we can rely on the previously extracted `(.bins record)`. 3. `sanitize-bin-value` and `desanitize-bin-value` don't need to transform a single item to a vector, apply a transducer on it while copying it into a second array and extract the first item from the result, this can be unrolled into a single `(get ...)` function. 4. Use `Map`'s methods to determine if it is a single bin, and avoid costly vector comparison. --- CHANGELOG.md | 4 ++++ .../clojure/aerospike_clj/aerospike_record.clj | 17 ++++++++++++----- src/main/clojure/aerospike_clj/utils.clj | 15 ++++----------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cd9dde..4108979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ### Unreleased +### VERSION 2.0.6 +#### Changed +* Performance and memory optimization, mainly in the core `aerospike-clj.aerospike-record/record->map` function. + ### VERSION 2.0.5 #### Changed * TTLs for the mock client are now correctly mocked: diff --git a/src/main/clojure/aerospike_clj/aerospike_record.clj b/src/main/clojure/aerospike_clj/aerospike_record.clj index f6058c4..289fced 100644 --- a/src/main/clojure/aerospike_clj/aerospike_record.clj +++ b/src/main/clojure/aerospike_clj/aerospike_record.clj @@ -1,16 +1,23 @@ (ns aerospike-clj.aerospike-record (:require [aerospike-clj.utils :as utils]) - (:import [com.aerospike.client Record])) + (:import (com.aerospike.client Record) + (java.util Map))) (defrecord AerospikeRecord [payload ^Integer gen ^Integer ttl]) +(defn- single-bin? + "Predicate function to determine whether data will be stored as a single bin or + multiple bin record." + [^Map bins] + (and (= (.size bins) 1) + (.containsKey bins ""))) + (defn record->map [^Record record] (and record - (let [bins (into {} (.bins record)) ;; converting from java.util.HashMap to a Clojure map - bin-names (keys bins) - payload (if (utils/single-bin? bin-names) + (let [bins ^Map (.bins record) + payload (if (single-bin? bins) ;; single bin record - (utils/desanitize-bin-value (get bins "")) + (utils/desanitize-bin-value (.get bins "")) ;; multiple-bin record (reduce-kv (fn [m k v] (assoc m k (utils/desanitize-bin-value v))) diff --git a/src/main/clojure/aerospike_clj/utils.clj b/src/main/clojure/aerospike_clj/utils.clj index c945601..0ba213e 100644 --- a/src/main/clojure/aerospike_clj/utils.clj +++ b/src/main/clojure/aerospike_clj/utils.clj @@ -1,6 +1,6 @@ (ns aerospike-clj.utils (:require [clojure.set :as set]) - (:import [java.util Collection])) + (:import (java.util Collection))) (def ^:private boolean-replacements "For bins, Aerospike converts `true` to `1`, `false` to `0` and `nil` values are @@ -11,12 +11,7 @@ false :false nil :nil}) -;; transducers -(def ^:private x-sanitize - (replace boolean-replacements)) - -(def ^:private x-desanitize - (replace (set/map-invert boolean-replacements))) +(def ^:private reverse-boolean-replacements (set/map-invert boolean-replacements)) ;; predicates (defn single-bin? @@ -35,14 +30,12 @@ however, `true`, `false` or `nil` exist as the only value in a bin, they need to be sanitized." [bin-value] - (->> (into [] x-sanitize (vector bin-value)) - first)) + (get boolean-replacements bin-value bin-value)) (defn desanitize-bin-value "Converts sanitized (keywordized) bin values back to their original value." [bin-value] - (->> (into [] x-desanitize (vector bin-value)) - first)) + (get reverse-boolean-replacements bin-value bin-value)) (defn v->array "An optimized way to convert vectors into Java arrays of type `clazz`."