最近研究了下google protobuf协议,顺便对比了一下json,xml,java序列化相关的数据对比,从几个纬度进行对比。
别人的相关测试数据: http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking
测试纬度
- 序列化时间
- 反序列化时间
- bytes大小
测试代码
准备protobuf文件
1.import "InnerMessage.proto";
2.package demo;
3.option java_package = "com.agapple.protobuf.data";
4.option java_outer_classname = "MessageProtos";
5.option optimize_for = SPEED ; //CODE_SIZE,LITE_RUNTIME
6.option java_generic_services = false;
7.message Message {
8.
9. required string strObj = 1 [default="hello"];
10. optional int32 int32Obj = 2;
11. optional int64 int64Obj = 3;
12. optional uint32 uint32Obj = 4;
13. optional uint64 uint64Obj = 5;
14. optional sint32 sint32Obj = 6;
15. optional sint64 sint64Obj = 7;
16. optional fixed32 fixed32Obj = 8;
17. optional fixed64 fixed64Obj = 9;
18. optional sfixed32 sfixed32Obj = 10;
19. optional sfixed64 sfixed64Obj = 11;
20. optional bool boolObj = 12;
21. optional bytes bytesObj = 13;
22. optional float folatObj = 14 [deprecated=true];
23. repeated double doubleObj = 15 [packed=true]; //
24. optional InnerMessage innerMessage = 16;
25.}
1.import "EnumType.proto";
2.
3.package demo;
4.option java_package = "com.agapple.protobuf.data";
5.option java_outer_classname = "InnerMessageProtos";
6.
7.message InnerMessage {
8. optional string name = 1 [default = "name"];
9. optional int32 id = 2;
10. optional EnumType type = 3 [default = UNIVERSAL];
11.}
1.package demo;
2.option java_package = "com.agapple.protobuf.data";
3.option java_outer_classname = "EnumTypeProtos";
4.
5.enum EnumType {
6. UNIVERSAL = 0;
7. WEB = 1;
8. IMAGES = 2;
9. LOCAL = 3;
10. NEWS = 4;
11. PRODUCTS = 5;
12. VIDEO = 6;
13.}
基本上把protobuf支持的类型都囊括了,包括嵌套类型,枚举类型,以及各种int,uint,bool,bytes。
依赖关系是Message.proto依赖了InnerMessage对象,而InnerMessage对象里包含了一个自定义枚举类型EnumType。
关于类型的使用可参见:
http://code.google.com/intl/zh/apis/protocolbuffers/docs/reference/java-generated.html
http://code.google.com/intl/zh/apis/protocolbuffers/docs/proto.html
生成protobuf javabean
1.cd /home/ljh/work/code/src/main/java
2.
3./home/ljh/work/protobuf/bin/protoc --proto_path=com/agapple/protobuf/ --java_out=. com/agapple/protobuf/EnumType.proto
4./home/ljh/work/protobuf/bin/protoc --proto_path=com/agapple/protobuf/ --java_out=. com/agapple/protobuf/InnerMessage.proto
5./home/ljh/work/protobuf/bin/protoc --proto_path=com/agapple/protobuf/ --java_out=. com/agapple/protobuf/Message.proto
通过protobuf自带的protoc进行编译,指定了protobuf文件的路径, 具体的文档: http://code.google.com/intl/zh/apis/protocolbuffers/docs/proto.html#generating
运行脚本后就会生成对应的3个javabean文件: MessageProtos , InnerMessageProtos , EnumTypeProtos。
最后构造测试的protobuf bean代码
1.private static MessageProtos.Message getProtobufBean() {
2. com.agapple.protobuf.data.MessageProtos.Message.Builder messageBuilder = MessageProtos.Message.newBuilder();
3.
4. messageBuilder.setStrObj("message");
5. messageBuilder.setFolatObj(1f);
6. messageBuilder.addDoubleObj(1d);
7. messageBuilder.addDoubleObj(2d);
8. messageBuilder.setBoolObj(true);
9.
10. messageBuilder.setBytesObj(ByteString.copyFrom(new byte[] { 1, 2, 3 }));
11. messageBuilder.setInt32Obj(32);
12. messageBuilder.setInt64Obj(64l);
13. messageBuilder.setSint32Obj(232);
14. messageBuilder.setSint64Obj(264);
15. messageBuilder.setFixed32Obj(532);
16. messageBuilder.setFixed64Obj(564);
17. messageBuilder.setSfixed32Obj(2532);
18. messageBuilder.setSfixed64Obj(2564);
19. messageBuilder.setUint32Obj(632);
20. messageBuilder.setUint64Obj(664);
21.
22. com.agapple.protobuf.data.InnerMessageProtos.InnerMessage.Builder innerMessageBuilder = InnerMessageProtos.InnerMessage.newBuilder();
23. innerMessageBuilder.setId(1);
24. innerMessageBuilder.setName("inner");
25. innerMessageBuilder.setType(EnumType.PRODUCTS);
26.
27. messageBuilder.setInnerMessage(innerMessageBuilder);
28.
29. return messageBuilder.build();
30. }
准备纯Pojo Bean
同样的,为了和json , xml以及java序列化有个很好的对比,新建了3个纯的pojo bean: MessagePojo , InnerMessagePojo , EnumTypePojo。
属性和proto的bean保持一致。
构建bean对象
1.private static MessagePojo getPojoBean() {
2. MessagePojo bean = new MessagePojo();
3.
4. bean.setStrObj("message");
5. bean.setFolatObj(1f);
6. List<Double> doubleObj = new ArrayList<Double>();
7. doubleObj.add(1d);
8. doubleObj.add(2d);
9. bean.setDoubleObj(doubleObj);
10. bean.setBoolObj(true);
11.
12. bean.setBytesObj(new byte[] { 1, 2, 3 });
13. bean.setInt32Obj(32);
14. bean.setInt64Obj(64l);
15. bean.setSint32Obj(232);
16. bean.setSint64Obj(264);
17. bean.setFixed32Obj(532);
18. bean.setFixed64Obj(564);
19. bean.setSfixed32Obj(2532);
20. bean.setSfixed64Obj(2564);
21. bean.setUint32Obj(632);
22. bean.setUint64Obj(664);
23.
24. InnerMessagePojo innerMessagePojo = new InnerMessagePojo();
25. innerMessagePojo.setId(1);
26. innerMessagePojo.setName("inner");
27. innerMessagePojo.setType(EnumTypePojo.PRODUCTS);
28.
29. bean.setInnerMessage(innerMessagePojo);
30.
31. return bean;
32. }
具体的测试代码
定义测试Template接口
1.interface TestCallback {
2.
3. String getName();
4.
5. byte[] writeObject(Object source);
6.
7. Object readObject(byte[] bytes);
8.}
统一的测试模板
1.private static void testTemplate(TestCallback callback, Object source, int count) {
2. int warmup = 10;
3. // 先进行预热,加载一些类,避免影响测试
4. for (int i = 0; i < warmup; i++) {
5. byte[] bytes = callback.writeObject(source);
6. callback.readObject(bytes);
7. }
8. restoreJvm(); // 进行GC回收
9. // 进行测试
10. long start = System.nanoTime();
11. long size = 0l;
12. for (int i = 0; i < count; i++) {
13. byte[] bytes = callback.writeObject(source);
14. size = size + bytes.length;
15. callback.readObject(bytes);
16. // System.out.println(callback.readObject(bytes));
17. bytes = null;
18. }
19. long nscost = (System.nanoTime() - start);
20. System.out.println(callback.getName() + " total cost=" + integerFormat.format(nscost) + "ns , each cost="
21. + integerFormat.format(nscost / count) + "ns , and byte sizes = " + size / count);
22. restoreJvm();// 进行GC回收
23.
24. }
在测试模板方法中,使用了warmup预热的概念,就是预先执行目标方法一定的次数,用于避免因为jit的优化影响系统测试。 同时包含了每次测试模板调用完成后system.gc保证下一轮的功能测试
相应的restoreJvm方法:
1.private static void restoreJvm() {
2. int maxRestoreJvmLoops = 10;
3. long memUsedPrev = memoryUsed();
4. for (int i = 0; i < maxRestoreJvmLoops; i++) {
5. System.runFinalization();
6. System.gc();
7.
8. long memUsedNow = memoryUsed();
9. // break early if have no more finalization and get constant mem used
10. if ((ManagementFactory.getMemoryMXBean().getObjectPendingFinalizationCount() == 0)
11. && (memUsedNow >= memUsedPrev)) {
12. break;
13. } else {
14. memUsedPrev = memUsedNow;
15. }
16. }
17. }
18.
19. private static long memoryUsed() {
20. Runtime rt = Runtime.getRuntime();
21. return rt.totalMemory() - rt.freeMemory();
22. }
最后相应的测试例子:
1.final int testCount = 1000 * 500;
2.final MessageProtos.Message protoObj = getProtobufBean();
3.final MessagePojo pojoOBj = getPojoBean();
4.
5.// Serializable测试
6.testTemplate(new TestCallback() {
7.
8. public String getName() {
9. return "Serializable Test";
10. }
11.
12. @Override
13. public byte[] writeObject(Object source) {
14. try {
15. ByteArrayOutputStream bout = new ByteArrayOutputStream();
16. ObjectOutputStream output = new ObjectOutputStream(bout);
17. output.writeObject(source);
18. return bout.toByteArray();
19. } catch (IOException e) {
20. e.printStackTrace();
21. }
22. return null;
23. }
24.
25. @Override
26. public Object readObject(byte[] bytes) {
27. try {
28. ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
29. ObjectInputStream input = new ObjectInputStream(bin);
30. return input.readObject();
31. } catch (Exception e) {
32. e.printStackTrace();
33. }
34. return null;
35. }
36.}, pojoOBj, testCount);
37.
38.// protobuf测试
39.testTemplate(new TestCallback() {
40.
41. public String getName() {
42. return "protobuf test";
43. }
44.
45. @Override
46. public byte[] writeObject(Object source) {
47. if (source instanceof MessageProtos.Message) {
48. MessageProtos.Message message = (MessageProtos.Message) source;
49. return message.toByteArray();
50. }
51.
52. return null;
53. }
54.
55. @Override
56. public Object readObject(byte[] bytes) {
57. try {
58. return MessageProtos.Message.parseFrom(bytes);
59. } catch (InvalidProtocolBufferException e) {
60. e.printStackTrace();
61. }
62. return null;
63. }
64.}, protoObj, testCount);
65.
66.// json测试
67.final ObjectMapper objectMapper = new ObjectMapper();
68.final JavaType javaType = TypeFactory.type(pojoOBj.getClass());
69.
70.// JSON configuration not to serialize null field
71.objectMapper.getSerializationConfig().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
72.
73.// JSON configuration not to throw exception on empty bean class
74.objectMapper.getSerializationConfig().disable(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS);
75.
76.// JSON configuration for compatibility
77.objectMapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
78.objectMapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
79.
80.testTemplate(new TestCallback() {
81.
82. public String getName() {
83. return "Jackson Test";
84. }
85.
86. @Override
87. public byte[] writeObject(Object source) {
88. try {
89. return objectMapper.writeValueAsBytes(source);
90. } catch (JsonGenerationException e) {
91. e.printStackTrace();
92. } catch (JsonMappingException e) {
93. e.printStackTrace();
94. } catch (IOException e) {
95. e.printStackTrace();
96. }
97.
98. return null;
99. }
100.
101. @Override
102. public Object readObject(byte[] bytes) {
103. try {
104. return objectMapper.readValue(bytes, 0, bytes.length, javaType);
105. } catch (JsonParseException e) {
106. e.printStackTrace();
107. } catch (JsonMappingException e) {
108. e.printStackTrace();
109. } catch (IOException e) {
110. e.printStackTrace();
111. }
112. return null;
113. }
114.}, pojoOBj, testCount);
115.
116.// Xstream测试
117.final XStream xstream = new XStream();
118.testTemplate(new TestCallback() {
119.
120. public String getName() {
121. return "Xstream test";
122. }
123.
124. @Override
125. public byte[] writeObject(Object source) {
126. return xstream.toXML(source).getBytes();
127. }
128.
129. @Override
130. public Object readObject(byte[] bytes) {
131. return xstream.fromXML(new ByteArrayInputStream(bytes));
132. }
133.}, pojoOBj, testCount);
2011年3月10号补充 =========================================================
增加了hessian 3.1.5版本基于二进制序列化的测试
1.<dependency>
2. <groupId>com.caucho</groupId>
3. <artifactId>hessian</artifactId>
4. <version>3.1.5</version>
5.</dependency>
测试了3种情况:
- hessian 2协议
- hessian 2协议 + deflat压缩
- hessian 1协议
测试代码:
1.// hessian 2 with no deflat
2. testTemplate(new TestCallback() {
3.
4. public String getName() {
5. return "hessian 2 with no deflat";
6. }
7.
8. @Override
9. public byte[] writeObject(Object source) {
10. try {
11. ByteArrayOutputStream bos = new ByteArrayOutputStream();
12. Hessian2Output out = new Hessian2Output(bos);
13. // out.startMessage();
14. out.writeObject(source);
15. // out.completeMessage();
16. out.flush();
17. return bos.toByteArray();
18. } catch (IOException e) {
19. e.printStackTrace();
20. }
21. return null;
22. }
23.
24. @Override
25. public Object readObject(byte[] bytes) {
26. try {
27. ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
28. Hessian2Input in = new Hessian2Input(bin);
29. // in.startMessage();
30. Object obj = in.readObject();
31. // in.completeMessage();
32. return obj;
33. } catch (IOException e) {
34. e.printStackTrace();
35. }
36. return null;
37. }
38. }, pojoOBj, testCount);
39.
40. // hessian 2 with deflat
41. final Deflation envelope = new Deflation();
42. testTemplate(new TestCallback() {
43.
44. public String getName() {
45. return "hessian 2 with deflat";
46. }
47.
48. @Override
49. public byte[] writeObject(Object source) {
50. try {
51. ByteArrayOutputStream bos = new ByteArrayOutputStream();
52. Hessian2Output out = new Hessian2Output(bos);
53. out = envelope.wrap(out);
54. out.writeObject(source);
55. out.flush();
56. out.close(); // 记得关闭
57. return bos.toByteArray();
58. } catch (Exception e) {
59. e.printStackTrace();
60. }
61. return null;
62. }
63.
64. @Override
65. public Object readObject(byte[] bytes) {
66. try {
67. ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
68. Hessian2Input in = new Hessian2Input(bin);
69. in = envelope.unwrap(in);
70. Object obj = in.readObject();
71. in.close();
72. return obj;
73. } catch (IOException e) {
74. e.printStackTrace();
75. }
76. return null;
77. }
78. }, pojoOBj, testCount);
79.
80. // hessian 1 with no deflat
81. testTemplate(new TestCallback() {
82.
83. public String getName() {
84. return "hessian 1 with no deflat";
85. }
86.
87. @Override
88. public byte[] writeObject(Object source) {
89. try {
90. ByteArrayOutputStream bos = new ByteArrayOutputStream();
91. HessianOutput out = new HessianOutput(bos);
92. out.writeObject(source);
93. out.flush();
94. return bos.toByteArray();
95. } catch (Exception e) {
96. e.printStackTrace();
97. }
98. return null;
99. }
100.
101. @Override
102. public Object readObject(byte[] bytes) {
103. try {
104. ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
105. HessianInput in = new HessianInput(bin);
106. Object obj = in.readObject();
107. in.close();
108. return obj;
109. } catch (IOException e) {
110. e.printStackTrace();
111. }
112. return null;
113. }
114. }, pojoOBj, testCount);
测试结果
序列化数据对比
bytes字节数对比
具体的数字:
protobuf | jackson | xstream | Serializable | hessian2 | hessian2压缩 | hessian1 | |
序列化(单位ns) | 1154 | 5421 | 92406 | 10189 | 26794 | 100766 | 29027 |
反序列化(单位ns) | 1334 | 8743 | 117329 | 64027 | 37871 | 188432 | 37596 |
bytes | 97 | 311 | 664 | 824 | 374 | 283 | 495 |
- protobuf 不管是处理时间上,还是空间占用上都优于现有的其他序列化方式。内存暂用是java 序列化的1/9,时间也是差了一个数量级,一次操作在1us左右。缺点:就是对象结构体有限制,只适合于内部系统使用。
- json格式在空间占用还是有一些优势,是java序列化的1/2.6。序列化和反序列化处理时间上差不多,也就在5us。当然这次使用的jackson,如果使用普通的jsonlib可能没有这样好的性能,jsonlib估计跟java序列化差不多。
- xml相比于java序列化来说,空间占用上有点优势,但不明显。处理时间上比java序列化多了一个数量级,在100us左右。
- 以前一种的java序列化,表现得有些失望
- hessian测试有点意外,具体序列化数据上还步入json。性能上也不如jackjson,输得比较彻底。
- hessian使用压缩,虽然在字节上有20%以上的空间提升,但性能上差了4,5倍,典型的以时间换空间。总的来说还是google protobuf比较给力
总结
以后在内部系统,数据cache存储上可以考虑使用protobuf。跟外部系统交互上可以考虑使用json。
有兴趣的同学,可以研究一下google protobuf的marshall的方式: http://code.google.com/intl/zh/apis/protocolbuffers/docs/encoding.html