zoukankan      html  css  js  c++  java
  • java序列化之-性能优化

      在最近的计划中,打算看看在不使用google protobuf的情况下,在原有的采用jackson作为json序列化工具的基础上,是否可以实现进一步的性能优化。主要是针对list的情况,在一些包含比较大的对象比如有上百个对象的列表序列化、反序列化的逻辑中,有一个序列化+反序列化操作,他们加起来时间占据了接近1/3,由此可见为了达到高TPS,序列化的性能和大小也是不可忽视的

      测试的时候选择了一个50个字段的对象,采用50条记录的list作为例子。因为大部分还都是可控的系统rpc交互,所以测试的时候选择了将字段用逗号分隔的方式。

      在反射机制中,Reflection和BeanInfo两种均作了测试,method/field都做了缓存的前提,结果中与原生jackson序列化、反序列化性能相差在20%以内,并无明显的提升。

      同时,在测试中将class缓存后性能提升约10%,将setAccessible设置为true后性能提升也在10%左右。字符串的解析并没有太大的性能消耗,反而是invoke消耗了绝大部分的性能。

      所以,至少就原生的Java反射机制而言,性能并没有明显的提升(当然,性能跟字段数还是有一定的关系,如果字段数较少、行数没有那么多,那么class每次加载的比例可能会增加)。

      除了json外,还可以考虑kryo序列化方式,其性能比json高出不少,同时没有pb/flatbuffer一样的额外结构维护要求。如下:

            // 序列化、反序列化性能测试
            UserRole userRole = new UserRole();
            userRole.setRoleId(1L);
            Role role = new Role();
            role.setRoleCode("roleCode");
            role.setCurrentUserId(1L);
            role.setSystemRoleFlag(true);
            role.setTenantCode("ta");
            userRole.setRole(role);
            User user =new User();
            user.setPassPercent(10);
            user.setAddress("wwwfw");
            user.setPassModifytime(new Date());
            userRole.setUser(user);
            UserRole newUserRole = null;
            // kryo序列化
            long beg = System.currentTimeMillis();
            for (int i=0;i<100000;i++) {
                newUserRole = deserialize(serialize(userRole));
            }
            System.out.println(System.currentTimeMillis()-beg); // 1443毫秒
            // jdk序列化
            beg = System.currentTimeMillis();
            for (int i=0;i<100000;i++) {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectOutputStream os = new ObjectOutputStream(bos);
                os.writeObject(userRole);
                os.flush();
                os.close();
                byte[] b = bos.toByteArray();
                bos.close();
                ByteArrayInputStream bis = new ByteArrayInputStream(b);
                ObjectInputStream is = new ObjectInputStream(bis);
                newUserRole = (UserRole) is.readObject();
            }
            System.out.println(System.currentTimeMillis()-beg); // 8000毫秒
    
            // TODO 自定义序列化,非性能极端场景不要使用,太不够灵活
    
            // json序列化
            beg = System.currentTimeMillis();
            for (int i=0;i<100000;i++) {
                newUserRole = JsonUtils.json2Object(JsonUtils.toJson(userRole),UserRole.class);
            }
            System.out.println(System.currentTimeMillis()-beg); // 3934毫秒
            // 性能测试结束

      对于序列化来说,要考虑的是兼容性。在开发中,增减字段是常见的事,由于我们会使用大量的缓存,因此如何在结构变化后保持兼容性非常重要的。对于JDK序列化来说,只要serialVersionUID相同,就可以无缝处理。如下:

    @Getter@Setter
    public class ShareDetail implements Serializable {
        private static final long serialVersionUID = 1000L;
    
        private String tableName;
        private String status;
        // 动态改变
        private Date date;
    }
            // 结构兼容性测试
            ////        ShareDetail sh = new ShareDetail();
    //        sh.setTableName("t");
    //        sh.setStatus("s");
    //        FileOutputStream bos = new FileOutputStream("d:\javaserial.dat");
    //        ObjectOutputStream os = new ObjectOutputStream(bos);
    //        os.writeObject(sh);
    //        os.flush();
    //        os.close();
    //        bos.close();
            // 读,新增Date字段
            FileInputStream bis = new FileInputStream("d:\javaserial.dat");
            ObjectInputStream is = new ObjectInputStream(bis);
            // 没有serialVersionUID的话,报Exception in thread "main" java.io.InvalidClassException: com.hundsun.ta.base.service.ShareDetail; local class incompatible: stream classdesc serialVersionUID = 161953476132204792, local class serialVersionUID = 3990225612413777011
            ShareDetail sh = (ShareDetail) is.readObject();
            System.out.println(sh.getStatus());
    /**
         * Kryo本身非线程安全,需要使用线程本地变量,也可以线程池,参见https://www.jianshu.com/p/f56c9360936d
         */
        private static ThreadLocal<Kryo> kryo = new ThreadLocal<Kryo>() {
            @Override
            protected Kryo initialValue() {
                Kryo kryo = new Kryo();
                kryo.setDefaultSerializer(CompatibleFieldSerializer.class);
                return kryo;
            }
        };
    
        /**
         * 注意越界 https://m.2cto.com/kf/201612/573016.htmlhttps://www.jianshu.com/p/43008038866c
         * @param o
         * @return
         */
        public static byte[] serialize(Object o) {
            Output output = new Output(4096,65536);
            kryo.get().writeClassAndObject(output, o);
            byte[] bytes = output.toBytes();
            output.flush();
            output.close();
            return bytes;
        }
    
        /**
         * T 定义修改后,默认会报Exception in thread "main" com.esotericsoftware.kryo.KryoException: Encountered unregistered class ID: 13994
         * 需使用CompatibleFieldSerializer序列化器。在目标类上增加@DefaultSerializer(CompatibleFieldSerializer.class)也行
         * 字段顺序的变化不会导致反序列化失败,因为写入的时候会排序
         * 相关其他限制可以参考https://www.cnblogs.com/hntyzgn/p/7122709.html
         * @param bytes
         * @param <T>
         * @return
         */
        public static  <T> T deserialize(byte[] bytes) {
            Input input = new Input(bytes);
            T o = (T)kryo.get().readClassAndObject(input);
            return o;
        }

    // 使用CompatibleFieldSerializer序列化支持向前或向后兼容。

            // kryo
    //        FileOutputStream bos = new FileOutputStream("d:\kryo.dat");
    //        ShareDetail sh = new ShareDetail();
    //        sh.setTableName("t");
    //        sh.setStatus("s");
    //        bos.write(serialize(sh));
    //        bos.flush();
    //        bos.close();
    
            FileInputStream bis = new FileInputStream("d:\kryo.dat");
            byte[] buffer = new byte[74];
            int len = bis.read(buffer);
            ShareDetail sh = deserialize(buffer);
            System.out.println(sh.getStatus());
  • 相关阅读:
    C++ 11 右值引用以及std::move
    poj2299--B
    Linux Socket编程注意事项
    Using Qt to build an Omi App for iOS (and Android)
    openwrt 3g模块上网
    详谈隐藏Tabbar的几种方法
    ZOJ 3529 A Game Between Alice and Bob(博弈论-sg函数)
    uva 10574
    【MySQL案例】HA: GTID_MODE配置不一致
    Swift UIView 层次调整
  • 原文地址:https://www.cnblogs.com/zhjh256/p/6306103.html
Copyright © 2011-2022 走看看