
暂时不想支持很多特性,只想如何最高效地把Json反序列化成对象.
目前成果还比较初步,但初衷已经满足了. 开源出来请大家提出任何意见以继续改进.
性能测试程序及版本(均为目前最新发布版本):
Jackson: 2.10.2
FastJson: 1.2.62
Dsl-Json: 1.9.5
Jsoniter: 0.9.23 (含javassist: 3.26.0)
Jason: 当前版本(2020-02-25 22:58:35)
性能测试结果(时间越短越好) (测试环境: AdoptOpenJDK 13.0.2, 默认的JVM参数):
Jackson: 5884ms (使用Afterburner插件实时生成字节码)
FastJson: 4299ms (实时生成字节码)
Dsl-Json: 3799ms (非生成代码版本,生成代码版本用起来很繁琐,有人测过请在评论中留言)
Jsoniter: 2182ms (实时生成代码)
Jason: 1841ms (无生成代码,使用高性能反射)
测试一轮(反序列化1000万个对象)的内存分配量:
Jackson: 8016MB
FastJson: 4428MB
Dsl-Json: 1530MB
Jsoniter: 1374MB (除了反序列化的对象本身之外几乎无开销)
Jason: 1374MB (除了反序列化的对象本身之外几乎无开销)
性能测试代码:
import com.alibaba.fastjson.JSON;
import com.dslplatform.json.DslJson;
import com.dslplatform.json.runtime.Settings;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import com.jsoniter.JsonIterator;
import com.jsoniter.spi.DecodingMode;
import jason.Jason;
public class BenchJson {
public static class C { // 测试的类
public int a;
public String b;
public C c;
}
// 测试的JSON字符串
static String str = "{\"b\":\"xyz\", \"a\":123,\"c\":{\"a\":456,\"b\":\"abc\"}}";
static byte[] bytes = str.getBytes();
static final int TEST_COUNT = 10_000_000;
static void testJackson() throws Exception { // jackson-*-2.10.2.jar四个文件总大小1.93MB
long t = System.nanoTime(), v = 0;
ObjectMapper om = new ObjectMapper();
om.registerModule(new AfterburnerModule()); // 实测这里Afterburner插件效果不理想,只提速15%
for (int i = 0; i < TEST_COUNT; ++i) {
C c = om.readValue(bytes, C.class);
v += c.a + c.c.a;
}
System.out.println(" Jackson: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
}
static void testFastJson() { // fastjson-1.2.62.jar大小643KB
long t = System.nanoTime(), v = 0;
for (int i = 0; i < TEST_COUNT; ++i) {
C c = JSON.parseObject(str, C.class); // 只有FastJson使用String输入比byte[]性能更高
v += c.a + c.c.a;
}
System.out.println("FastJson: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
}
static void testDslJson() throws Exception { // dsl-json-1.9.5.jar和dsl-json-java8-1.9.5.jar总大小468KB
long t = System.nanoTime(), v = 0;
DslJson<Object> dj = new DslJson<>(Settings.withRuntime());
for (int i = 0; i < TEST_COUNT; ++i) {
C c = dj.deserialize(C.class, bytes, bytes.length);
v += c.a + c.c.a;
}
System.out.println("Dsl-Json: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
}
static void testJsoniter() { // jsoniter-0.9.23.jar和javassist-3.26.0-GA.jar总大小1.08MB
long t = System.nanoTime(), v = 0;
// 设置最快的模式,需要借助javassist库(javassist-3.26.0-GA.jar大小765KB)
JsonIterator.setMode(DecodingMode.DYNAMIC_MODE_AND_MATCH_FIELD_WITH_HASH);
for (int i = 0; i < TEST_COUNT; ++i) {
C c = JsonIterator.deserialize(bytes, C.class);
v += c.a + c.c.a;
}
System.out.println("Jsoniter: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
}
static void testJason() throws Exception { // jason.jar大小25KB
long t = System.nanoTime(), v = 0;
Jason jason = new Jason();
for (int i = 0; i < TEST_COUNT; ++i) {
C c = jason.buf(bytes).parse(C.class);
v += c.a + c.c.a;
}
System.out.println(" Jason: " + v + ", " + (System.nanoTime() - t) / 1_000_000 + "ms");
}
public static void main(String[] args) throws Exception {
// 各测试5轮,排除预热影响,以最少时间为准
for (int i = 0; i < 5; ++i)
testJackson();
for (int i = 0; i < 5; ++i)
testFastJson();
for (int i = 0; i < 5; ++i)
testDslJson();
for (int i = 0; i < 5; ++i)
testJsoniter();
for (int i = 0; i < 5; ++i)
testJason();
}
}
目前还只是初始版本(只有一千多行代码,支持JDK8及更高版本), Json的解析算法暂时只参考了RapidJSON的数字解析. 后续还会再学习其它实现中的优秀之处.
补充一些说明:
-
上面的性能测试是在AMD的CPU上统计的, 之后在Intel的CPU上测出的结果有些出入, 主要是Jsoniter表现得更好了, 结果跟Jason很接近, 不得不佩服Jsoniter的性能.
-
如果有人现在需要生产用途的Json库,推荐用Jsoniter,代码质量也比FastJson好不少, 可惜Jsoniter已经有一段时间没更新维护了, 不知道何时出1.0版本.
-
Jsoniter的代码编译使用了Javassist库, 但我认为可以直接调用JDK内置的编译, 做到完全无三方依赖.
-
Jason支持JDK8以上, 但推荐运行在JDK9以上. 因为Jason用了一些JDK9才有的优化, 而且一些底层调用在JDK9以上能更高效地运行.
-
Jason的数字解析参考了目前最快的RapidJSON的实现, 也做了少量Java特定的优化, 最终跟C++版本的性能差距不大了. 比Jsoniter的相关实现快一些.
-
Jason除了支持JSON标准之外, 还支持忽略多余的逗号, 支持不带引号的key. 其它扩展(如注释)的支持待考虑. 另外Jason对不标准的JSON有一定程度的容忍, 但这是快速解析的副作用,不能当做feature.另外Jason目前只支持UTF-8编码的字节数组作为输入.
-
Jason支持了动态确定类型的parser. 有两种方法, 一种见其中的Parser类(推荐), 另一种见其中的Pos类.
-
Jason用了很多JDK中Unsafe的调用, 这个是非公开API, 因此不支持Android.
-
Jason还缺乏充足的测试代码, 后续会逐渐补充.
-
Jason后续有打算做序列化功能. 但不确定何时完成.
-
Jason有打算增加文档, 如果有足够关注的话.
最后
