当前位置:   article > 正文

Android 性能优化--Protobuf使用及原理_将 /usr/local/lib 路径写入libprotobuf.conf

将 /usr/local/lib 路径写入libprotobuf.conf

Android 性能优化–Protobuf使用及原理

相关文章:Gson和Fastjson的使用
最新 文章连接,本文不再同步

1. 什么是Protobuf?

1.1 优点

  • 性能

    • 体积小,序列化后,数据大小可缩小3-10倍
    • 序列化速度快,比XML和JSON快20-100倍
    • 传输速度快,因为体积小,传输起来带宽和速度会有优化
  • 使用

    • 使用简单,proto编译器自动进行序列化和反序列化
    • 维护成本低,多平台仅需维护一套对象协议文件(.proto)
    • 向后兼容性(扩展性)好,不必破坏旧数据格式就可以直接对数据结构进行更新
    • 加密性好,Http传输内容抓包只能看到字节
  • 使用范围:跨平台、跨语言(支持Java, Python, Objective-C, C+, Dart, Go, Ruby,
    and C#等
    ),可扩展性好

1.2 缺点

  • 功能,不适合用于对基于文本的标记文档(如HTML)建模,因为文本不适合描述数据结构
  • 通用性较差:json、xml已成为多种行业标准的编写工具,而Protobuf只是Google公司内部的工具
  • 自解耦性差:以二进制数据流方式存储(不可读),需要通过.proto文件才能了解到数据结构

1.3 应用场景

  • 传输数据量大 & 网络环境不稳定 的数据存储、RPC 数据交换 的需求场景
    如 即时IM (QQ、微信)的需求场景
  • 在传输数据量较大的需求场景下,Protobuf比XML、Json 更小、更快、使用 & 维护更简单!
  • AS中就有使用protobuf,参见文件Android Studio\lib\protobuf-java-3.4.0.jar

2. 使用

2.1 安装 protoc

protoc 是编译*.proto为相应语言的工具

2.1.1 ubuntu 安装 protoc
  • 方法一 apt-get安装
    使用 sudo apt-get install protoc 安装
  • 方法二 下载源码编译安装
  1. github下载*.tar.tz压缩包,解压,
  2. 命令行进入解压后目录,执行如下指令编译 ./configure && make
  3. 创建文件 /etc/ld.so.conf.d/libprotobuf.conf 写入内容:/usr/local/lib
    输入命令 sudo ldconfig
    注:protobuf的默认安装路径是/usr/local/lib,而/usr/local/lib不在Ubuntu体系默认的 LD_LIBRARY_PATH 里,后面install找不到该lib
  4. sudo make install
  5. protoc --version 验证是否安装成功
2.1.2 windows安装 protoc
  1. 官方下载windows版本解压
  2. 把里面的protoc.exe所在目录,添加到环境变量

2.2 使用Android的Gradle插件编译

protobuf-gradle-plugin官方介绍:https://github.com/google/protobuf-gradle-plugin

  • 修改项目根目录下build.gradle
    dependencies下增加classpath ‘com.google.protobuf:protobuf-gradle-plugin:0.8.8’
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.0'
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 修改app目录下build.gradle
    apply plugin: 'com.android.application'后加上apply plugin: 'com.google.protobuf'

  • 然后加入protobuf配置(与android{}同级)

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.0.0'
    }
    plugins {
        javalite {
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                remove java
            }
            task.plugins {
                javalite { }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

3. Protobuf序列化、反序列化的性能对比

参见源码MainActivity.java 中函数protoTest()

相对FastJson和Gson的对比参考代码同上,结果如下

E/MainActivity: protobuf 序列化耗时:4
E/MainActivity: protobuf 序列化数据大小:38
E/MainActivity: protobuf 反序列化耗时:2
E/JsonTest: FastJson 序列化耗时:13
E/JsonTest: FastJson 序列化数据大小:119
E/JsonTest: FastJson 反序列化耗时:16
E/JsonTest: Gson 序列化耗时:14
E/JsonTest: Gson 序列化数据大小:119
E/JsonTest: Gson 反序列化耗时:1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4. Protobuf 原理

4.1 protobuf 数据结构

  • 采用 TLV存储方式,即TAG-Length-Value(标识-长度-字段值)
  • 不需要分隔符就能分隔开字段,减少了分隔符的使用;
  • 各字段存储得非常紧凑,存储空间利用率非常高;
  • 若字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要编码

4.2 protobuf 编码方式

对于不同数据类型 采用不同的 序列化方式(编码方式 & 数据存储方式)

4.3 TAG 编码过程

Tag = (field_number << 3) | wire_type
wire_type 的值参考4.2,field_number就是字段标识号,详细如下代码

    //wire_type=0, field_number=1
    //Tag  = (field_number << 3) | wire_type
    // TAG=8
    required int32 id1 = 1;
    
    //wire_type=0, field_number=2
    //Tag  = (field_number << 3) | wire_type
    // TAG=16
    required int32 id2 = 2;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4.4 Varint 编码过程

可参考源码CodedOutputStream.java
中1724行函数 writeUInt32NoTag

点我下载查看Varint 编码过程原图

4.5 Varint 解码过程

4.6 Zigzag 编码过程

4.7 总结建议

  • 尽量使用多用 optional或 repeated修饰符
    因为若optional 或 repeated 字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要进行编码

  • 字段标识号(Field_Number)尽量只使用 1-15,且不要跳动使用
    因为Tag里的Field_Number是需要占字节空间的。如果Field_Number>16时,Field_Number的编码就会占用2个字节,那么Tag在编码时也就会占用更多的字节;如果将字段标识号定义为连续递增的数值,将获得更好的编码和解码性能

  • 若需要使用的字段值出现负数,请使用 sint32 / sint64,不要使用int32 / int64
    因为采用sint32 / sint64数据类型表示负数时,会先采用Zigzag编码再采用Varint编码,从而更加有效压缩数据

  • 对于repeated字段,尽量增加packed=true修饰
    因为加了packed=true修饰repeated字段采用连续数据存储方式,即T - L - V - V -V方式

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/134354
推荐阅读
相关标签
  

闽ICP备14008679号