ObjectMapper, stop being new like a second-hand!

picture

Original: Miss Sister Taste (WeChat public account ID: xjjdog), welcome to share, non-public account reprints keep this statement.

Since the domestic light fastjson has frequently thundered, the use of jackson json has become more and more extensive. In particular, the spring family has made it the default JSON processing package, and the number of jackson usage has exploded.

Many students found that jackson does not have a method similar to fastjson, JSON.parseObjecwhich does seem to be very fast. To parse json, you have to create a new ObjectMapper to handle the actual parsing action.

Like below.

public String getCarString(Car car){
    ObjectMapper objectMapper = new ObjectMapper();
    String str = objectMapper.writeValueAsString(car);
    return str;
}

复制代码

This kind of code is blooming everywhere in the hands of CV engineers.

magic.

Is there something wrong with this code?

You have to say it has a problem, it does execute correctly. You have to say that it is no problem. In the eyes of students who pursue performance, this must be a heinous piece of code.

General tool classes are singletons and thread-safe at the same time. ObjectMapper is no exception, it is also thread-safe, you can execute it concurrently without any problems.

In this code, ObjectMapper will generate one each time the method is called. In addition to causing a certain amount of memory waste in the young generation, is there any flaw in the execution time?

Is there really such a big difference between new and not new?

Once, xjjdog cryptically pointed out a code problem that was frequently called, and was roared by a friend to show evidence.

evidence? This has to be moved out of the benchmarking tool JMH in Java to find out.

JMH (the Java Microbenchmark Harness) is such a tool for benchmarking. If you have located a hot code through our series of tools, and want to test its performance data and evaluate the improvement, you can hand it over to JMH. Its measurement accuracy is very high, up to the nanosecond level.

JMHIt is a jar package, which JUnitis very similar to the unit testing framework, and can perform some basic configuration through annotations. There are many configurations in this part that can be set through the main method OptionsBuilder.

picture

上图是一个典型的JMH程序执行的内容。通过开启多个进程,多个线程,首先执行预热,然后执行迭代,最后汇总所有的测试数据进行分析。在执行前后,还可以根据粒度处理一些前置和后置操作。

JMH测试结果

为了测试上面的场景,我们创造了下面的基准测试类。分为三个测试场景:

  1. 直接在方法里new ObjectMapper

  2. 在全局共享一个ObjectMapper

  3. 使用ThreadLocal,每个线程一个ObjectMapper

这样的测试属于cpu密集型的。我的cpu有10核,直接就分配了10个线程的并发,cpu在测试期间跑的满满的。

@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(10)
public class ObjectMapperTest {
    String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";

    @State(Scope.Benchmark)
    public static class BenchmarkState {
        ObjectMapper GLOBAL_MAP = new ObjectMapper();
        ThreadLocal<ObjectMapper> GLOBAL_MAP_THREAD = new ThreadLocal<>();
    }

    @Benchmark
    public Map globalTest(BenchmarkState state) throws Exception{
        Map map = state.GLOBAL_MAP.readValue(json, Map.class);
        return map;
    }


    @Benchmark
    public Map globalTestThreadLocal(BenchmarkState state) throws Exception{
        if(null == state.GLOBAL_MAP_THREAD.get()){
            state.GLOBAL_MAP_THREAD.set(new ObjectMapper());
        }
        Map map = state.GLOBAL_MAP_THREAD.get().readValue(json, Map.class);
        return map;
    }

    @Benchmark
    public Map localTest() throws Exception{
        ObjectMapper objectMapper = new ObjectMapper();
        Map map = objectMapper.readValue(json, Map.class);
        return map;
    }

    public static void main(String[] args) throws Exception {
        Options opts = new OptionsBuilder()
                .include(ObjectMapperTest.class.getSimpleName())
                .resultFormat(ResultFormatType.CSV)
                .build();

        new Runner(opts).run();
    }
}

复制代码

测试结果如下。

Benchmark                                Mode  Cnt         Score         Error  Units
ObjectMapperTest.globalTest             thrpt    5  25125094.559 ± 1754308.010  ops/s
ObjectMapperTest.globalTestThreadLocal  thrpt    5  31780573.549 ± 7779240.155  ops/s
ObjectMapperTest.localTest              thrpt    5   2131394.345 ±  216974.682  ops/s

复制代码

从测试结果可以看出,如果我们每次调用都new一个ObjectMapper,每秒可以执行200万次JSON解析;如果全局使用一个ObjectMapper,则每秒可以执行2000多万次,速度足足快了10倍。

如果使用ThreadLocal的方式,每个线程给它分配一个解析器,则性能会有少许上升,但也没有达到非常夸张的地步。

所以在项目中写代码的时候,我们只需要保证有一个全局的ObjectMapper就可以了。

当然,由于ObjectMapper有很多的特性需要配置,你可能会为不同的应用场景分配一个单独使用的ObjectMapper。总之,它的数量不需要太多,因为它是线程安全的。

End

所以结论就比较清晰了,我们只需要在整个项目里使用一个ObjectMapper就可以了,没必要傻不拉几的每次都new一个,毕竟性能差了10倍。如果你的JSON有很多自定义的配置,使用全局的变量更能凸显它的优势。

不要觉得这样做没有必要,保持良好的编码习惯永远是好的。高性能的代码都是点点滴滴积累起来的。不积跬步,无以至千里。不积小流,无以成江海,说的就是这个道理。

About the author: Miss Sister Taste  (xjjdog), a public account that does not allow programmers to take detours. Focus on infrastructure and Linux. Ten years of architecture, tens of billions of daily traffic, discussing the high concurrency world with you, giving you a different taste. My personal WeChat xjjdog0, welcome to add friends for further communication.

Recommended reading:

1. Play Linux
2. What Taste Album

3.  Bluetooth is like a dream
4.  Murder!
5.  The architect who lost contact left only a script
6.  The bug written by the architect is unusual
7.  Some programmers are essentially a flock of sheep!

Miss sister taste

Don't envy mandarin ducks or immortals, just adjust a line of code for a long time

333 original content

Guess you like

Origin juejin.im/post/7086443387636678670