Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster de-serialization of Maps and Vectors #60

Merged
merged 3 commits into from
Dec 7, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
welcome java-types + ctx-sharing others
  • Loading branch information
ikitommi committed Dec 6, 2021
commit 47d7133b78e1ac9d8a723e09f348421bfd94530b
4 changes: 2 additions & 2 deletions benchmarks.edn
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{:benchmarks [{:name :encode
:ns jsonista.jmh
:fn [encode-data-json encode-cheshire encode-jsonista encode-jackson]
:fn [encode-data-json encode-cheshire encode-jsonista encode-jackson encode-jsonista-fast]
:args [:state/edn]}
{:name :decode
:ns jsonista.jmh
:fn [decode-data-json decode-cheshire decode-jsonista decode-jackson]
:fn [decode-data-json decode-cheshire decode-jsonista decode-jackson decode-jsonista-fast]
:args [:state/json]}]
:states {:json {:fn jsonista.jmh/json-data, :args [:param/size]}
:edn {:fn jsonista.jmh/edn-data, :args [:param/size]}}
Expand Down
1 change: 1 addition & 0 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
[org.msgpack/msgpack-core "0.9.0"]
[org.msgpack/jackson-dataformat-msgpack "0.9.0"
:exclusions [com.fasterxml.jackson.core/jackson-databind]]
[com.clojure-goes-fast/clj-async-profiler "0.5.0"]
[criterium "0.4.6"]]
:global-vars {*warn-on-reflection* true}}
:virgil {:plugins [[lein-virgil "0.1.9"]]}
Expand Down
9 changes: 9 additions & 0 deletions src/clj/jsonista/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
FunctionalSerializer
KeywordSerializer
KeywordKeyDeserializer
HashMapDeserializer
ArrayListDeserializer
PersistentHashMapDeserializer
PersistentVectorDeserializer
SymbolSerializer
Expand Down Expand Up @@ -98,6 +100,13 @@
(true? encode-key-fn) (.addKeySerializer Keyword (KeywordSerializer. true))
(fn? encode-key-fn) (.addKeySerializer Keyword (FunctionalKeywordSerializer. encode-key-fn)))))

(defn ^:no-doc ^Module java-collection-module
"Create a Jackson Databind module to support Java HashMap and ArrayList."
[]
(doto (SimpleModule. "JavaCollectionModule")
(.addDeserializer List (ArrayListDeserializer.))
(.addDeserializer Map (HashMapDeserializer.))))

(defn ^ObjectMapper object-mapper
"Create an ObjectMapper with Clojure support.

Expand Down
48 changes: 48 additions & 0 deletions src/java/jsonista/jackson/ArrayListDeserializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package jsonista.jackson;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class ArrayListDeserializer extends StdDeserializer<List<Object>> implements ContextualDeserializer {

private JsonDeserializer<Object> _valueDeserializer;

public ArrayListDeserializer() {
super(List.class);
}

public ArrayListDeserializer(JsonDeserializer<Object> valueDeser) {
this();
_valueDeserializer = valueDeser;
}

@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty beanProperty) throws JsonMappingException {
JavaType object = ctxt.constructType(Object.class);
JsonDeserializer<Object> valueDeser = ctxt.findNonContextualValueDeserializer(object);
return this.withResolved(valueDeser);
}

private JsonDeserializer<List<Object>> withResolved(JsonDeserializer<Object> valueDeser) {
return this._valueDeserializer == valueDeser ? this : new ArrayListDeserializer(valueDeser);
}

@Override
@SuppressWarnings("unchecked")
public List<Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
ArrayList<Object> list = new ArrayList<>();

JsonDeserializer<Object> deser = ctxt.findNonContextualValueDeserializer(ctxt.constructType(Object.class));
while (p.nextValue() != JsonToken.END_ARRAY) {
list.add(deser.deserialize(p, ctxt));
}
return list;
}
}
52 changes: 52 additions & 0 deletions src/java/jsonista/jackson/HashMapDeserializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package jsonista.jackson;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class HashMapDeserializer extends StdDeserializer<Map<Object, Object>> implements ContextualDeserializer {

private KeyDeserializer _keyDeserializer;
private JsonDeserializer<?> _valueDeserializer;

public HashMapDeserializer() {
super(Map.class);
}

public HashMapDeserializer(KeyDeserializer keyDeser, JsonDeserializer<?> valueDeser) {
this();
_keyDeserializer = keyDeser;
_valueDeserializer = valueDeser;
}

protected HashMapDeserializer withResolved(KeyDeserializer keyDeser, JsonDeserializer<?> valueDeser) {
return this._keyDeserializer == keyDeser && this._valueDeserializer == valueDeser ? this : new HashMapDeserializer(keyDeser, valueDeser);
}

@Override
public JsonDeserializer<Map<Object, Object>> createContextual(DeserializationContext ctxt, BeanProperty beanProperty) throws JsonMappingException {
JavaType object = ctxt.constructType(Object.class);
KeyDeserializer keyDeser = ctxt.findKeyDeserializer(object, null);
JsonDeserializer<Object> valueDeser = ctxt.findNonContextualValueDeserializer(object);
return this.withResolved(keyDeser, valueDeser);
}

@Override
@SuppressWarnings("unchecked")
public Map<Object, Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
Map<Object, Object> map = new HashMap<>();
while (p.nextToken() != JsonToken.END_OBJECT) {
Object key = _keyDeserializer.deserializeKey(p.getCurrentName(), ctxt);
p.nextToken();
Object value = _valueDeserializer.deserialize(p, ctxt);
map.put(key, value);
}
return map;
}
}
36 changes: 26 additions & 10 deletions src/java/jsonista/jackson/PersistentHashMapDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,48 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.util.Map;

public class PersistentHashMapDeserializer extends StdDeserializer<Map<String, Object>> {
public class PersistentHashMapDeserializer extends StdDeserializer<Map<String, Object>> implements ContextualDeserializer {

private KeyDeserializer _keyDeserializer;
private JsonDeserializer<?> _valueDeserializer;

public PersistentHashMapDeserializer() {
super(Map.class);
}

public PersistentHashMapDeserializer(KeyDeserializer keyDeser, JsonDeserializer<?> valueDeser) {
this();
_keyDeserializer = keyDeser;
_valueDeserializer = valueDeser;
}

protected PersistentHashMapDeserializer withResolved(KeyDeserializer keyDeser, JsonDeserializer<?> valueDeser) {
return this._keyDeserializer == keyDeser && this._valueDeserializer == valueDeser ? this : new PersistentHashMapDeserializer(keyDeser, valueDeser);
}

@Override
@SuppressWarnings("unchecked")
public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ITransientMap t = PersistentHashMap.EMPTY.asTransient();
public JsonDeserializer<Map<String, Object>> createContextual(DeserializationContext ctxt, BeanProperty beanProperty) throws JsonMappingException {
JavaType object = ctxt.constructType(Object.class);
KeyDeserializer keyDeser = ctxt.findKeyDeserializer(object, null);
JsonDeserializer<Object> valueDeser = ctxt.findNonContextualValueDeserializer(object);
return this.withResolved(keyDeser, valueDeser);
}

@Override
@SuppressWarnings("unchecked")
public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ITransientMap t = PersistentHashMap.EMPTY.asTransient();
while (p.nextToken() != JsonToken.END_OBJECT) {
Object key = keyDeser.deserializeKey(p.getCurrentName(), ctxt);
Object key = _keyDeserializer.deserializeKey(p.getCurrentName(), ctxt);
p.nextToken();
Object value = valueDeser.deserialize(p, ctxt);
Object value = _valueDeserializer.deserialize(p, ctxt);
t = t.assoc(key, value);
}

Expand Down
29 changes: 23 additions & 6 deletions src/java/jsonista/jackson/PersistentVectorDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,45 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.util.List;
import java.util.Map;

public class PersistentVectorDeserializer extends StdDeserializer<List<Object>> {
public class PersistentVectorDeserializer extends StdDeserializer<List<Object>> implements ContextualDeserializer {

private JsonDeserializer<Object> _valueDeserializer;

public PersistentVectorDeserializer() {
super(List.class);
}

protected PersistentVectorDeserializer(JsonDeserializer<Object> valueDeser) {
this();
_valueDeserializer = valueDeser;
}

@Override
public JsonDeserializer<List<Object>> createContextual(DeserializationContext ctxt, BeanProperty beanProperty) throws JsonMappingException {
JavaType object = ctxt.constructType(Object.class);
JsonDeserializer<Object> valueDeser = ctxt.findNonContextualValueDeserializer(object);
return this.withResolved(valueDeser);
}

private JsonDeserializer<List<Object>> withResolved(JsonDeserializer<Object> valueDeser) {
return this._valueDeserializer == valueDeser ? this : new PersistentVectorDeserializer(valueDeser);
}

@Override
@SuppressWarnings("unchecked")
public List<Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ITransientCollection t = PersistentVector.EMPTY.asTransient();
JsonDeserializer<Object> deser = ctxt.findNonContextualValueDeserializer(ctxt.constructType(Object.class));
while (p.nextValue() != JsonToken.END_ARRAY) {
t = t.conj(deser.deserialize(p, ctxt));
t = t.conj(_valueDeserializer.deserialize(p, ctxt));
}
// t.persistent() returns a PersistentVector which is a list
return (List<Object>) t.persistent();
}
}
4 changes: 4 additions & 0 deletions test/jsonista/jmh.clj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
(defn encode-jsonista [x] (j/write-value-as-string x))
(defn decode-jsonista [x] (j/read-value x))

(let [mapper (j/object-mapper {:modules [(j/java-collection-module)]})]
(defn encode-jsonista-fast [x] (.writeValueAsString mapper x))
(defn decode-jsonista-fast [x] (.readValue mapper ^String x ^Class Object)))

(let [mapper (ObjectMapper.)]
(defn encode-jackson [x] (.writeValueAsString mapper x))
(defn decode-jackson [x] (.readValue mapper ^String x ^Class Object)))
Expand Down
20 changes: 10 additions & 10 deletions test/jsonista/json_perf_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,12 @@
(assert (= +data+ (cheshire/parse-string-strict +json+)))
(cc/quick-bench (cheshire/parse-string-strict +json+))

;; 390ns
;; 300ns
(title "decode: jsonista")
(assert (= +data+ (j/read-value +json+)))
(cc/quick-bench (j/read-value +json+))

;; 230ns
;; 260ns
(title "decode: jackson")
(let [mapper (ObjectMapper.)
decode (fn [] (.readValue mapper +json+ Map))]
Expand Down Expand Up @@ -118,20 +118,20 @@

(title file)

; 1.0µs (10b)
; 0.9µs (10b)
; 1.9µs (100b)
; 9.2µs (1k)
; 102µs (10k)
; 990µs (100k)
; 110µs (10k)
; 1000µs (100k)
(title "decode: cheshire")
(assert (= data (cheshire/parse-string json)))
(cc/quick-bench (cheshire/parse-string json))

; 0.40µs (10b)
; 1.5µs (100b)
; 7.1µs (1k)
; 77µs (10k)
; 760µs (100k)
; 0.32µs (10b)
; 1.3µs (100b)
; 5.9µs (1k)
; 70µs (10k)
; 680µs (100k)
(title "decode: jsonista")
(assert (= data (j/read-value json)))
(cc/quick-bench (j/read-value json))))
Expand Down