我们一起聊聊对象与 Map 转换性能优化方案

开发 前端
通过将 ObjectMapper​ 设计为单例模式,我们可以显著提升性能。在整个项目中只需创建一个 ObjectMapper 实例,避免了每次使用时重新创建对象所带来的开销。这种方法不仅提高了性能,还简化了代码管理和减少了内存消耗。

前言

有粉丝提到每次都需要new一个ObjectMapper对象,并且提到性能压测。

Person person = new Person();
person.setAge(18);
person.setOpenid("123456");
person.setName("一安");
person.setSubName("公众号");

System.out.println(bean2Map(person));

public static Map<String, Object> bean2Map(Object object) {
    ObjectMapper objectMapper = new ObjectMapper();
    return objectMapper.convertValue(object, new TypeReference<Map<String, Object>>() {
    });
}

改造

首先,我们将使用 Java Microbenchmark Harness (JMH) 对这段代码进行基准测试,以便大家对其性能有一个直观的了解。

图片图片

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

JMH 是 Java 语言的微基准测试框架,用于准确、可靠地测量和评估Java代码的性能。它是由OpenJDK团队开发的,专门针对Java应用程序的性能测试和基准测试。通过JMH 可以对多个方法的性能进行定量分析。比如,当要知道执行一个函数需要多少时间,或者当对一个算法有多种不同实现时,需要选取性能最好的那个。

依赖引入

<!-- JMH核心代码 -->
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.35</version>
</dependency>

<!-- JMH注解相关依赖 -->
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.35</version>
</dependency>

注解说明

BenchmarkMode

名称

解释

单位

Mode.Throughput

operations per unit of time.(单位时间执行的次数)

ops/time

Mode.AverageTime

average time per per operation(每个方法执行的平均时间)

time/op

Mode.SampleTime

samples the time for each operation.(每个方法执行的时间)

time

Mode.SingleShotTime

measures the time for a single operation.(单个的执行时间)


All

all the benchmark modes. (上面所有都执行一次)


OutputTimeUnit

统计的时间单位

Warmup、Measurement

名称

解释

iterations

预热次数

time

预热时间

timeUnit

预热时间单位

batchSize

同时预热

State

名称

解释

Benchmark

所有测试共享线程。做多线程的时候使用

Group

每一组中共享线程

Thread

每一个方法或者类共享线程

Fork

进行次数,如果 fork 数是2的话,则 JMH 会 fork 出两个进程来进行测试

Benchmark

表示该方法是需要进行 benchmark 的对象,用法和 JUnit 的 @Test 类似

测试验证

@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 3, time = 1)
public class JsonJMHTest {


    @Benchmark
    public static Map<String, Object> bean2Map() {
        Person person = new Person();
        person.setAge(18);
        person.setOpenid("123456");
        person.setName("一安");
        person.setSubName("公众号");

        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.convertValue(person, new TypeReference<Map<String, Object>>() {
        });
    }

    @Benchmark
    public static <T> T map2Bean() {
        Map<String, Object> map = new HashMap();
        map.put("age", 18);
        map.put("openid", "123456");
        map.put("name", "一安");
        map.put("subName", "公众号");

        ObjectMapper objectMapper = new ObjectMapper();
        return (T) objectMapper.convertValue(map, Person.class);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(JsonJMHTest.class.getSimpleName())
                .build();
        new Runner(opt).run();
    }

测试结果:

# Fork: 1 of 1
# Warmup Iteration   1: 4121.577 ops/s
# Warmup Iteration   2: 10599.791 ops/s
# Warmup Iteration   3: 1945.716 ops/s
# Warmup Iteration   4: 7284.198 ops/s
# Warmup Iteration   5: 8161.620 ops/s
Iteration   1: 841.544 ops/s
Iteration   2: 25483.108 ops/s
Iteration   3: 70902.482 ops/s


Result "org.example.JsonJMHTest.map2Bean":
  32409.045 ±(99.9%) 648386.677 ops/s [Average]
  (min, avg, max) = (841.544, 32409.045, 70902.482), stdev = 35540.262
  CI (99.9%): [≈ 0, 680795.722] (assumes normal distribution)


# Run complete. Total time: 00:00:22

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark              Mode  Cnt      Score        Error  Units
JsonJMHTest.bean2Map  thrpt    3  80269.397 ±  61739.014  ops/s
JsonJMHTest.map2Bean  thrpt    3  32409.045 ± 648386.677  ops/s

通过测试结果可以看出,在实现对象转map时每秒可以完成8万多次,而实现map转对象转每秒仅可完成3.2万次。

如何优化

我们都知道在创建工具类时,应将其设计为单例模式,保证在整个系统中仅有一个实例,从而避免因频繁创建对象而带来的成本。

@Getter
public enum ObjectMapperInstance {
    INSTANCE;
    private final ObjectMapper objectMapper = new ObjectMapper();
    ObjectMapperInstance() {
    }
}

枚举类型的单例实现天然线程安全,并且可以抵御反射攻击。

再次测试验证

# Fork: 1 of 1
# Warmup Iteration   1: 916836.618 ops/s
# Warmup Iteration   2: 2057459.265 ops/s
# Warmup Iteration   3: 1992614.947 ops/s
# Warmup Iteration   4: 524763.395 ops/s
# Warmup Iteration   5: 2463816.439 ops/s
Iteration   1: 2570659.849 ops/s
Iteration   2: 2557669.589 ops/s
Iteration   3: 2548610.266 ops/s


Result "org.example.JsonJMHTest.map2Bean":
  2558979.901 ±(99.9%) 202195.856 ops/s [Average]
  (min, avg, max) = (2548610.266, 2558979.901, 2570659.849), stdev = 11083.037
  CI (99.9%): [2356784.046, 2761175.757] (assumes normal distribution)


# Run complete. Total time: 00:00:30

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark              Mode  Cnt        Score          Error  Units
JsonJMHTest.bean2Map  thrpt    3  1107857.325 ± 19284117.404  ops/s
JsonJMHTest.map2Bean  thrpt    3  2558979.901 ±   202195.856  ops/s

通过将 ObjectMapper 设计为单例模式,我们可以显著提升性能。在整个项目中只需创建一个 ObjectMapper 实例,避免了每次使用时重新创建对象所带来的开销。这种方法不仅提高了性能,还简化了代码管理和减少了内存消耗。

同样的原则可以应用于其他工具类或频繁使用的对象。例如,数据库连接池、缓存客户端、日志记录器等,都可以通过单例模式来优化性能和资源管理。确保这些组件在整个应用生命周期内只创建一次,可以最大化其效用并减少不必要的资源消耗。

总之,将频繁使用的工具类设计为单例模式是一种良好的编程实践,它不仅提升了性能,还增强了代码的可维护性和可读性。

责任编辑:武晓燕 来源: 一安未来
相关推荐

2024-07-11 08:26:00

2024-02-26 00:00:00

Go性能工具

2023-12-29 08:29:15

QPS系统应用

2022-04-06 08:23:57

指针函数代码

2023-07-14 12:28:07

JVM优化操作

2023-03-29 08:13:48

MySQL检索成本

2024-02-26 00:00:00

架构老化重构

2021-11-04 06:58:31

CSS性能设备

2024-06-12 09:52:00

2022-05-24 08:21:16

数据安全API

2023-08-10 08:28:46

网络编程通信

2023-08-04 08:20:56

DockerfileDocker工具

2023-06-30 08:18:51

敏捷开发模式

2023-09-10 21:42:31

2021-08-27 07:06:10

IOJava抽象

2024-02-20 21:34:16

循环GolangGo

2022-11-03 07:51:54

运维体系监控

2023-12-06 08:26:19

Service数据库

2024-01-29 09:01:20

React列表模式

2023-07-04 08:06:40

数据库容器公有云
点赞
收藏

51CTO技术栈公众号