Biorę na tapetę według mnie najlepsze serializatory w javie:
- Kryo – https://github.com/EsotericSoftware/kryo
- Fst – https://github.com/RuedigerMoeller/fast-serialization
- Google Protobuf – https://github.com/google/protobuf
- protostuff.io – http://www.protostuff.io
- Jackson – https://github.com/FasterXML/jackson
Serializuję dwa obiekty zawierające stringa (1470 bytes) i Double.MAX_VALUE
Wyniki benchmarku:
Benchmark Mode Cnt Score Error Units SerializeBenchmark.fst thrpt 5 0,477 ± 0,039 ops/ms SerializeBenchmark.google thrpt 5 910,106 ± 60,102 ops/ms SerializeBenchmark.jackson thrpt 5 66,483 ± 12,103 ops/ms SerializeBenchmark.kryo thrpt 5 92,880 ± 27,383 ops/ms SerializeBenchmark.protostuff thrpt 5 187,986 ± 12,292 ops/ms
A teraz puścimy to w 5 wątkach:
Benchmark Mode Cnt Score Error Units SerializeBenchmark.fst thrpt 5 2,111 ± 0,804 ops/ms SerializeBenchmark.google thrpt 5 971,071 ± 63,001 ops/ms SerializeBenchmark.jackson thrpt 5 252,071 ± 16,351 ops/ms SerializeBenchmark.kryo thrpt 5 232,511 ± 87,517 ops/ms SerializeBenchmark.protostuff thrpt 5 7483,938 ± 1240,685 ops/ms
A teraz 50 wątków 🙂
Benchmark Mode Cnt Score Error Units SerializeBenchmark.fst thrpt 5 1,897 ± 0,292 ops/ms SerializeBenchmark.google thrpt 5 959,975 ± 107,916 ops/ms SerializeBenchmark.jackson thrpt 5 261,513 ± 13,936 ops/ms SerializeBenchmark.kryo thrpt 5 308,221 ± 37,969 ops/ms SerializeBenchmark.protostuff thrpt 5 7742,153 ± 121,602 ops/ms
Jak widać na wynikach, puszczając serializację w jednym wątku Google Protobuf radzi sobie najlepiej natomiast ilość wątków w żaden sposób nie przyspiesza jago działania czego nie można powiedzieć o Protostuff.io gdzie wzrost był diametralny.
Zmniejszanie wielkości stringa do 59 bytes przełożyło się na szybkość wykonywania się Google Protobuf i Kryo:
Benchmark Mode Cnt Score Error Units SerializeBenchmark.fst thrpt 5 1,941 ± 0,351 ops/ms SerializeBenchmark.google thrpt 5 8555,033 ± 488,605 ops/ms SerializeBenchmark.jackson thrpt 5 644,608 ± 33,522 ops/ms SerializeBenchmark.kryo thrpt 5 4497,039 ± 194,688 ops/ms SerializeBenchmark.protostuff thrpt 5 7271,828 ± 471,228 ops/ms
Jeszcze wyniki z SampleTime:
Result "fst": N = 10545 mean = 23674,701 ±(99.9%) 589,445 us/op Histogram, us/op: [ 0,000, 12500,000) = 3261 [ 12500,000, 25000,000) = 2955 [ 25000,000, 37500,000) = 2231 [ 37500,000, 50000,000) = 1238 [ 50000,000, 62500,000) = 487 [ 62500,000, 75000,000) = 217 [ 75000,000, 87500,000) = 81 [ 87500,000, 100000,000) = 40 [100000,000, 112500,000) = 24 [112500,000, 125000,000) = 5 [125000,000, 137500,000) = 4 [137500,000, 150000,000) = 1 [150000,000, 162500,000) = 1 [162500,000, 175000,000) = 0 [175000,000, 187500,000) = 0 Percentiles, us/op: p(0,0000) = 1689,600 us/op p(50,0000) = 20938,752 us/op p(90,0000) = 47448,064 us/op p(95,0000) = 57389,875 us/op p(99,0000) = 82646,139 us/op p(99,9000) = 114866,520 us/op p(99,9900) = 153048,370 us/op p(99,9990) = 153878,528 us/op p(99,9999) = 153878,528 us/op p(100,0000) = 153878,528 us/op
Result "google": N = 2502277 mean = 51,858 ±(99.9%) 4,069 us/op Histogram, us/op: [ 0,000, 25000,000) = 2500907 [ 25000,000, 50000,000) = 471 [ 50000,000, 75000,000) = 299 [ 75000,000, 100000,000) = 268 [100000,000, 125000,000) = 174 [125000,000, 150000,000) = 114 [150000,000, 175000,000) = 35 [175000,000, 200000,000) = 7 [200000,000, 225000,000) = 2 [225000,000, 250000,000) = 0 [250000,000, 275000,000) = 0 Percentiles, us/op: p(0,0000) = 0,585 us/op p(50,0000) = 6,448 us/op p(90,0000) = 8,352 us/op p(95,0000) = 9,072 us/op p(99,0000) = 13,136 us/op p(99,9000) = 1653,645 us/op p(99,9900) = 110171,836 us/op p(99,9990) = 164072,286 us/op p(99,9999) = 202751,066 us/op p(100,0000) = 215482,368 us/op
Result "jackson": N = 1327485 mean = 189,506 ±(99.9%) 10,396 us/op Histogram, us/op: [ 0,000, 25000,000) = 1324594 [ 25000,000, 50000,000) = 881 [ 50000,000, 75000,000) = 965 [ 75000,000, 100000,000) = 600 [100000,000, 125000,000) = 266 [125000,000, 150000,000) = 100 [150000,000, 175000,000) = 30 [175000,000, 200000,000) = 19 [200000,000, 225000,000) = 20 [225000,000, 250000,000) = 7 [250000,000, 275000,000) = 3 Percentiles, us/op: p(0,0000) = 12,944 us/op p(50,0000) = 27,168 us/op p(90,0000) = 29,600 us/op p(95,0000) = 30,976 us/op p(99,0000) = 37,952 us/op p(99,9000) = 66225,046 us/op p(99,9900) = 134283,631 us/op p(99,9990) = 214886,027 us/op p(99,9999) = 260584,932 us/op p(100,0000) = 261357,568 us/op
Result "kryo": N = 1469072 mean = 167,398 ±(99.9%) 9,844 us/op Histogram, us/op: [ 0,000, 25000,000) = 1466513 [ 25000,000, 50000,000) = 723 [ 50000,000, 75000,000) = 772 [ 75000,000, 100000,000) = 505 [100000,000, 125000,000) = 275 [125000,000, 150000,000) = 117 [150000,000, 175000,000) = 74 [175000,000, 200000,000) = 53 [200000,000, 225000,000) = 27 [225000,000, 250000,000) = 11 [250000,000, 275000,000) = 2 Percentiles, us/op: p(0,0000) = 10,736 us/op p(50,0000) = 23,520 us/op p(90,0000) = 26,368 us/op p(95,0000) = 27,616 us/op p(99,0000) = 32,960 us/op p(99,9000) = 60555,264 us/op p(99,9900) = 157024,256 us/op p(99,9990) = 222441,334 us/op p(99,9999) = 264054,662 us/op p(100,0000) = 273154,048 us/op
Result "protostuff": N = 2535291 mean = 7,083 ±(99.9%) 1,455 us/op Histogram, us/op: [ 0,000, 25000,000) = 2535095 [ 25000,000, 50000,000) = 57 [ 50000,000, 75000,000) = 61 [ 75000,000, 100000,000) = 42 [100000,000, 125000,000) = 19 [125000,000, 150000,000) = 11 [150000,000, 175000,000) = 4 [175000,000, 200000,000) = 1 [200000,000, 225000,000) = 1 [225000,000, 250000,000) = 0 [250000,000, 275000,000) = 0 Percentiles, us/op: p(0,0000) = 0,004 us/op p(50,0000) = 0,831 us/op p(90,0000) = 1,468 us/op p(95,0000) = 1,684 us/op p(99,0000) = 2,212 us/op p(99,9000) = 6,230 us/op p(99,9900) = 10534,912 us/op p(99,9990) = 109360,353 us/op p(99,9999) = 172171,596 us/op p(100,0000) = 204210,176 us/op
Benchmark Mode Cnt Score Error Units SerializeBenchmark.fst sample 10545 23674,701 ± 589,445 us/op SerializeBenchmark.google sample 2502277 51,858 ± 4,069 us/op SerializeBenchmark.jackson sample 1327485 189,506 ± 10,396 us/op SerializeBenchmark.kryo sample 1469072 167,398 ± 9,844 us/op SerializeBenchmark.protostuff sample 2535291 7,083 ± 1,455 us/op
Wnioskują, jedynymi godnymi rozważania serializatorami są google i protostuff natomiast google serializator wymaga wygenerowania klas na podstawie schematu opracowanego w pliku .proto a w przypadku protostuff wykorzystujemy RuntimeSchema gdzie nie musimy się zastanawiać nad nowo dodanymi polami w klasie.
Benchmarki wykonane za pomocą OpenJDK JMH z parametrami: -XX:+UseG1GC -Xms1g -Xmx1g
Przykładowe kawłki kodu:
Fast serializer:
public void fst () { OnHeapCoder coder = new OnHeapCoder(false, StandardObject.class); byte barray[] = coder.toByteArray(standardObject); standardObject = (StandardObject) coder.toObject(barray); }
Google:
public void google () { byte[] barray = googleObject.toByteArray(); try { googleObject = ObiektOuterClass.Obiekt.parseFrom(barray); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); } }
Jackson:
public void jackson() throws Exception { String string = mapper.writeValueAsString(standardObject); StandardObject result = mapper.readValue(string, StandardObject.class); }
Kryo:
public void kryo () { Output output = new Output(5, 5000); kryo.writeObject(output, standardObject); byte[] bytes = output.toBytes(); output.clear(); Input input = new Input(bytes); standardObject = kryo.readObject(input, StandardObject.class); }
Protostaff:
public void protostuff () { byte[] barray = ProtostuffIOUtil.toByteArray(standardObject, schema, LinkedBuffer.allocate()); standardObject = new StandardObject(); ProtostuffIOUtil.mergeFrom(barray, standardObject, schema); }
Obiekt serializowany (dla google analogiczny ale wygenerowany):
public class StandardObject implements Serializable { @Tag(value = 1, alias = "v") private double val; @Tag(value = 2, alias = "o") private StandardObject standardObject; @Tag(value = 3, alias = "s") private String string; public StandardObject getStandardObject() { return standardObject; } public void setStandardObject(StandardObject standardObject) { this.standardObject = standardObject; } public double getVal() { return val; } public void setVal(double val) { this.val = val; } public String getString() { return string; } public void setString(String string) { this.string = string; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; StandardObject that = (StandardObject) o; return Double.compare(that.val, val) == 0 && Objects.equals(standardObject, that.standardObject) && Objects.equals(string, that.string); } @Override public int hashCode() { return Objects.hash(val, standardObject, string); } @Override public String toString() { return "StandardObject{" + "val=" + val + ", standardObject=" + standardObject + '}'; } }