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 +
'}';
}
}