Skip to content

Commit

Permalink
Merge pull request #60 from metosin/tatu_faster
Browse files Browse the repository at this point in the history
Faster de-serialization of Maps and Vectors
  • Loading branch information
ikitommi authored Dec 7, 2021
2 parents 7f8cba3 + ec52192 commit 29e5e54
Show file tree
Hide file tree
Showing 15 changed files with 233 additions and 75 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## UNRELEASED

* implement `com.fasterxml.jackson.databind.deser.ContextualDeserializer` for 30% faster de-serialization of Maps and Vectors.
* Updated deps:

```clj
[com.fasterxml.jackson.core/jackson-core "2.13.0"] is available but we use "2.12.5"
[com.fasterxml.jackson.core/jackson-databind "2.13.0"] is available but we use "2.12.5"
[com.fasterxml.jackson.datatype/jackson-datatype-jsr310 "2.13.0"] is available but we use "2.12.5"
```

## 0.3.4 (2021-09-16)

* add `deps.edn` to the project
Expand Down
84 changes: 42 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,48 +159,48 @@ see [perf-tests](/test/jsonista/jmh.clj) for details.
➜ jsonista git:(master) ✗ lein jmh '{:file "benchmarks.edn", :type :quick, :format :table}'
{:% 100.0 :eta "00:00:00"}

:benchmark :name :mode :samples :score :score-error :params
----------------------------- ------- ----------- -------- ------------------ ------------ --------------
jsonista.jmh/encode-data-json :encode :throughput 5 1905463.089 ops/s 63122.305 {:size "10b"}
jsonista.jmh/encode-data-json :encode :throughput 5 345349.400 ops/s 175312.845 {:size "100b"}
jsonista.jmh/encode-data-json :encode :throughput 5 55137.457 ops/s 20339.204 {:size "1k"}
jsonista.jmh/encode-data-json :encode :throughput 5 5194.837 ops/s 395.984 {:size "10k"}
jsonista.jmh/encode-data-json :encode :throughput 5 539.111 ops/s 219.494 {:size "100k"}
jsonista.jmh/encode-cheshire :encode :throughput 5 965841.282 ops/s 384153.551 {:size "10b"}
jsonista.jmh/encode-cheshire :encode :throughput 5 414779.218 ops/s 77713.579 {:size "100b"}
jsonista.jmh/encode-cheshire :encode :throughput 5 95781.504 ops/s 16569.179 {:size "1k"}
jsonista.jmh/encode-cheshire :encode :throughput 5 9114.238 ops/s 824.365 {:size "10k"}
jsonista.jmh/encode-cheshire :encode :throughput 5 888.121 ops/s 190.972 {:size "100k"}
jsonista.jmh/encode-jsonista :encode :throughput 5 4354049.558 ops/s 1215677.321 {:size "10b"}
jsonista.jmh/encode-jsonista :encode :throughput 5 1698787.064 ops/s 940540.979 {:size "100b"}
jsonista.jmh/encode-jsonista :encode :throughput 5 376730.022 ops/s 154961.364 {:size "1k"}
jsonista.jmh/encode-jsonista :encode :throughput 5 29272.337 ops/s 5208.374 {:size "10k"}
jsonista.jmh/encode-jsonista :encode :throughput 5 2275.436 ops/s 1329.558 {:size "100k"}
jsonista.jmh/encode-jackson :encode :throughput 5 5471224.116 ops/s 2598678.138 {:size "10b"}
jsonista.jmh/encode-jackson :encode :throughput 5 1509416.980 ops/s 497578.108 {:size "100b"}
jsonista.jmh/encode-jackson :encode :throughput 5 386633.021 ops/s 1158.922 {:size "1k"}
jsonista.jmh/encode-jackson :encode :throughput 5 31780.542 ops/s 2320.829 {:size "10k"}
jsonista.jmh/encode-jackson :encode :throughput 5 2735.888 ops/s 406.603 {:size "100k"}
jsonista.jmh/decode-data-json :decode :throughput 5 2094889.246 ops/s 89077.322 {:size "10b"}
jsonista.jmh/decode-data-json :decode :throughput 5 485354.569 ops/s 23670.883 {:size "100b"}
jsonista.jmh/decode-data-json :decode :throughput 5 83811.653 ops/s 3133.162 {:size "1k"}
jsonista.jmh/decode-data-json :decode :throughput 5 7741.879 ops/s 155.608 {:size "10k"}
jsonista.jmh/decode-data-json :decode :throughput 5 797.233 ops/s 14.762 {:size "100k"}
jsonista.jmh/decode-cheshire :decode :throughput 5 1433093.847 ops/s 90976.064 {:size "10b"}
jsonista.jmh/decode-cheshire :decode :throughput 5 450070.726 ops/s 131950.612 {:size "100b"}
jsonista.jmh/decode-cheshire :decode :throughput 5 91872.057 ops/s 6432.237 {:size "1k"}
jsonista.jmh/decode-cheshire :decode :throughput 5 8509.100 ops/s 156.138 {:size "10k"}
jsonista.jmh/decode-cheshire :decode :throughput 5 887.882 ops/s 33.875 {:size "100k"}
jsonista.jmh/decode-jsonista :decode :throughput 5 2608856.640 ops/s 474804.764 {:size "10b"}
jsonista.jmh/decode-jsonista :decode :throughput 5 607787.943 ops/s 13728.880 {:size "100b"}
jsonista.jmh/decode-jsonista :decode :throughput 5 120542.299 ops/s 39147.980 {:size "1k"}
jsonista.jmh/decode-jsonista :decode :throughput 5 10827.731 ops/s 5616.145 {:size "10k"}
jsonista.jmh/decode-jsonista :decode :throughput 5 1128.572 ops/s 232.442 {:size "100k"}
jsonista.jmh/decode-jackson :decode :throughput 5 5526592.411 ops/s 1463758.000 {:size "10b"}
jsonista.jmh/decode-jackson :decode :throughput 5 1316257.448 ops/s 163658.831 {:size "100b"}
jsonista.jmh/decode-jackson :decode :throughput 5 252440.943 ops/s 14566.399 {:size "1k"}
jsonista.jmh/decode-jackson :decode :throughput 5 23871.633 ops/s 1625.629 {:size "10k"}
jsonista.jmh/decode-jackson :decode :throughput 5 2211.046 ops/s 91.282 {:size "100k"}
:benchmark :name :mode :samples :score :score-error :params
----------------------------- ------- ----------- -------- ------------------ ------------ --------------
jsonista.jmh/encode-data-json :encode :throughput 5 2011809.137 ops/s 12600.809 {:size "10b"}
jsonista.jmh/encode-data-json :encode :throughput 5 382677.707 ops/s 2861.142 {:size "100b"}
jsonista.jmh/encode-data-json :encode :throughput 5 66403.631 ops/s 597.436 {:size "1k"}
jsonista.jmh/encode-data-json :encode :throughput 5 5480.185 ops/s 58.379 {:size "10k"}
jsonista.jmh/encode-data-json :encode :throughput 5 576.691 ops/s 15.682 {:size "100k"}
jsonista.jmh/encode-cheshire :encode :throughput 5 996875.314 ops/s 5688.227 {:size "10b"}
jsonista.jmh/encode-cheshire :encode :throughput 5 482130.613 ops/s 2685.181 {:size "100b"}
jsonista.jmh/encode-cheshire :encode :throughput 5 128936.005 ops/s 879.709 {:size "1k"}
jsonista.jmh/encode-cheshire :encode :throughput 5 12209.066 ops/s 94.285 {:size "10k"}
jsonista.jmh/encode-cheshire :encode :throughput 5 1258.157 ops/s 12.340 {:size "100k"}
jsonista.jmh/encode-jsonista :encode :throughput 5 6356105.348 ops/s 85360.100 {:size "10b"}
jsonista.jmh/encode-jsonista :encode :throughput 5 2010379.039 ops/s 67648.165 {:size "100b"}
jsonista.jmh/encode-jsonista :encode :throughput 5 409264.663 ops/s 3704.992 {:size "1k"}
jsonista.jmh/encode-jsonista :encode :throughput 5 34527.245 ops/s 251.065 {:size "10k"}
jsonista.jmh/encode-jsonista :encode :throughput 5 2934.595 ops/s 15.858 {:size "100k"}
jsonista.jmh/encode-jackson :encode :throughput 5 6275467.563 ops/s 123578.482 {:size "10b"}
jsonista.jmh/encode-jackson :encode :throughput 5 2092035.098 ops/s 11417.613 {:size "100b"}
jsonista.jmh/encode-jackson :encode :throughput 5 408380.251 ops/s 10912.350 {:size "1k"}
jsonista.jmh/encode-jackson :encode :throughput 5 31992.554 ops/s 230.781 {:size "10k"}
jsonista.jmh/encode-jackson :encode :throughput 5 2887.485 ops/s 12.491 {:size "100k"}
jsonista.jmh/decode-data-json :decode :throughput 5 2257552.949 ops/s 23890.443 {:size "10b"}
jsonista.jmh/decode-data-json :decode :throughput 5 498261.935 ops/s 2348.572 {:size "100b"}
jsonista.jmh/decode-data-json :decode :throughput 5 85191.855 ops/s 321.961 {:size "1k"}
jsonista.jmh/decode-data-json :decode :throughput 5 7763.264 ops/s 250.502 {:size "10k"}
jsonista.jmh/decode-data-json :decode :throughput 5 771.691 ops/s 6.559 {:size "100k"}
jsonista.jmh/decode-cheshire :decode :throughput 5 1099821.870 ops/s 14796.659 {:size "10b"}
jsonista.jmh/decode-cheshire :decode :throughput 5 544013.773 ops/s 4122.539 {:size "100b"}
jsonista.jmh/decode-cheshire :decode :throughput 5 109517.975 ops/s 911.623 {:size "1k"}
jsonista.jmh/decode-cheshire :decode :throughput 5 10017.553 ops/s 50.871 {:size "10k"}
jsonista.jmh/decode-cheshire :decode :throughput 5 1014.003 ops/s 18.609 {:size "100k"}
jsonista.jmh/decode-jsonista :decode :throughput 5 3476196.425 ops/s 21535.641 {:size "10b"}
jsonista.jmh/decode-jsonista :decode :throughput 5 792773.466 ops/s 8209.591 {:size "100b"}
jsonista.jmh/decode-jsonista :decode :throughput 5 160180.797 ops/s 554.940 {:size "1k"}
jsonista.jmh/decode-jsonista :decode :throughput 5 14151.302 ops/s 107.906 {:size "10k"}
jsonista.jmh/decode-jsonista :decode :throughput 5 1508.829 ops/s 5.855 {:size "100k"}
jsonista.jmh/decode-jackson :decode :throughput 5 5145394.434 ops/s 84237.662 {:size "10b"}
jsonista.jmh/decode-jackson :decode :throughput 5 1339393.911 ops/s 6660.176 {:size "100b"}
jsonista.jmh/decode-jackson :decode :throughput 5 274465.912 ops/s 1589.614 {:size "1k"}
jsonista.jmh/decode-jackson :decode :throughput 5 29607.044 ops/s 183.068 {:size "10k"}
jsonista.jmh/decode-jackson :decode :throughput 5 2539.491 ops/s 17.753 {:size "100k"}
```

## Making a release
Expand Down
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
Binary file modified docs/json-decode-t.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/json-decode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/json-encode-t.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/json-encode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 6 additions & 5 deletions project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
:resource-paths ["resources"]
:javac-options ["-Xlint:unchecked" "-target" "1.8" "-source" "1.8"]
:java-source-paths ["src/java"]
:plugins [[lein-codox "0.10.7"]
:plugins [[lein-codox "0.10.8"]
[lein-jmh "0.3.0"]]
:deploy-repositories [["releases" {:url "https://repo.clojars.org/"
:sign-releases false
Expand All @@ -16,20 +16,21 @@
:codox {:source-uri "http://github.com/metosin/jsonista/blob/master/{filepath}#L{line}"
:output-path "doc"
:metadata {:doc/format :markdown}}
:dependencies [[com.fasterxml.jackson.core/jackson-core "2.12.5"]
[com.fasterxml.jackson.core/jackson-databind "2.12.5"]
[com.fasterxml.jackson.datatype/jackson-datatype-jsr310 "2.12.5"]]
:dependencies [[com.fasterxml.jackson.core/jackson-core "2.13.0"]
[com.fasterxml.jackson.core/jackson-databind "2.13.0"]
[com.fasterxml.jackson.datatype/jackson-datatype-jsr310 "2.13.0"]]
:profiles {:provided {:dependencies [[org.clojure/clojure "1.10.1"]]}
:dev {:dependencies [[org.clojure/clojure "1.10.1"]
[jmh-clojure/jmh-clojure "0.4.1"]
[com.fasterxml.jackson.datatype/jackson-datatype-joda "2.12.5"]
[com.fasterxml.jackson.datatype/jackson-datatype-joda "2.13.0"]
[cheshire "5.10.1"]
[com.taoensso/nippy "3.1.1"]
[org.clojure/data.json "2.4.0"]
[com.cognitect/transit-clj "1.0.324"]
[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.1"]
[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;
}
}
Loading

0 comments on commit 29e5e54

Please sign in to comment.