移动端混合开发,常用数据结构传输能力对比分析
Posted 360技术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了移动端混合开发,常用数据结构传输能力对比分析相关的知识,希望对你有一定的参考价值。
奇技指南
随着H5-Hybrid和Flutter开发热度的逐渐上升,各个VM之间的传输能力也是大家比较关心的问题之一。目前比较流行的传输结构有:Json,Protocol Buffers,Flat Buffers,本文对三种结构进行真机测试,通过实际数据来说明如何选择。
随着H5-Hybrid和Flutter开发热度的逐渐上升,各个VM之间的传输能力也是大家比较关心的问题之一。
目前比较流行的传输结构有:Json,Protocol Buffers,Flat Buffers,他们各自在android/Flutter/H5传输的性能表现如何,对三种传输结构如何进行选择。本文对三种结构进行真机测试,通过实际数据来说明如何选择。
01
测试样本介绍
测试样本选择:深度8层,150个字段组成,在日常业务中应该也是比较复杂的。
Json结构填充满数据大概是4KB的数据量
Protocol Buffers结构仅有2KB多点
Flat Buffers介于Protocol Buffers和Json之间。
02
Json、Protocol Buffers、Flat Buffers 结构分析
Json
1 "user":{"name":"mary"}
Json是一种K-V存储结构,在序列化和反序列化过程,会使用反射、遍历等方法,并且会产生一些临时对象,消耗一些内存,所以整体效率并不高效。
下面以Gson为例,简单分析一下反序列化过程:
对传入Java Class进行处理,反射查找类Field,并创建对应的BoundField。并为Field的序列化和反序列化寻找合适的TypeAdapter。
使用JsonReader对传入Json原始字符串进初步解析。
对JsonReader进行遍历,根据JsonReader.nextName的type类型,获取对应的TypeAdapter对数据进行解析,并赋值给对应的Object Field。
Protocol Buffers
1message User {
2 string name = 1;
3}
Protocol Buffers结构是类似KV的一种结构体TLV。优点是体积小,为了缩小体积,Protocol Buffers会采用多种编码方式优化压缩比。序列化和反序列化速度较快。
Pb存储方式如下图所示:
Tag算法是:field_number << 3 | wired_type,存储标识号和类型信息
value分为多种情况:
Varint类型的,不需要存Length,使用TV方式进行存储,针对不同的类型和数据大小,采用不同的编码方式内容进行编码压缩。适用int32, int64, bool, enum等类型。
对于Length delimited类型的,会采用TLV的方式进行存储。比如string, bytes, embedded messages, packed repeated fields。其中repeated存储方式使用packed=true标识,是TLVV…这样的结构。
Protocol Buffers反序列化过程需要注意的是,反序列化过程与字段顺序无关,是通过TV结构来保障数据的准确性。
反序列过程中需要对整个数据流进行解码,生成可用Object或Map映射。
Flat Buffers
Flat Buffers 使用偏移量标记的流式存储结构。引用Facebook比较火的一张图:
从图中大概可以看出数据是如何存储的。
Flat Buffers支持多种嵌套结构:structs, tables, vectors。其中tables支持向前后兼容并且支持字段可选。其特性适用于大多业务需求,后续测试数据也是基于tables进行的。
Flat Buffers序列化会提前计算出内容偏移量,根据起始偏移量和内容数据长度等信息逐步存储。因需要存入一些偏移量长度等描述信息,所以体积上比Protocol Buffers要大一些。数据存储方式是采用Little Endian。
序列化的写法比较繁琐。在Java中使用如下:
1// flat schema,table结构:
2table User {
3 name: string;
4 user: User;
5}
6// 数据填充
7FlatBufferBuilder builder = new FlatBufferBuilder(0);
8int name = builder.createString("mary");
9User.startUser(builder);
10User.addName(builder, name);
11int endUser = User.endUser();
12builder.finish(endUser);
Flat Buffers反序列化过程:
首先看一下内存中的数据(十六进制表):
每一个table结构都会包含两个比较重要的字段vtable_start和bb_ops。当Flat Object初始化时,在init方法中对ByteBuffer进行计算,获取对应table的vtable_start和bb_ops,为字段解析准备关键位信息。
真正访问table中的某个方法时,会对ByteBuffer进行动态解析。以name()方法说明,步骤如下:
首先通过vtable_start和vtable_offset(编译类Field里面的固定偏移量)获取到数据偏移量o。
使用o并结合bb_ops,计算出数据的起始位置和长度。
根据内容起始位置和长度,截取byte数据,进行解析返回。到这里name字段就被取出来了。
关键方法:
1 // api
2 public String name() {
3 int o = __offset(4);return o != 0 ? __string(o + bb_pos) : null;
4 }
5 // 获取o值
6 protected int __offset(int vtable_offset) {
7 return vtable_offset < vtable_size ? bb.getShort(vtable_start + vtable_offset) : 0;
8 }
9 // 解析内容
10 protected String __string(o+bb_ops) {
11 offset += bb.getInt(offset);
12 int length = bb.getInt(offset);
13 return utf8.decodeUtf8(bb, offset + SIZEOF_INT, length);
14 }
可以看出整个过程并没有涉及复杂的编解码和转换步骤。只是根据偏移量精准查找数据。所以解析速度非常高效。
03
传输通道分析
以上分析看来Flat Buffers和Protocol Buffers明显要优于Json。那么实际应用场景中又是如何?下面我们再看一下H5、Android、Flutter通道之间,在传输以上数据结构时,是怎么处理的。
常用的可选通道
首先介绍一下常用的可选通道:
Android和Flutter之间,目前还是通过Flutter官方提供的Channel通道来进行数据传输。
Android和H5之间,普遍做法是通过JsBridge,端上使用Webview.evaluatejavascript来调用H5,H5使用window.prompt调用端。现阶段Android端大部分使用的是Chrome的v8引擎。
Flutter和H5之间,是Flutter首先调用Android,再由Android调用H5。
android和flutter传输通道
三种结构在传输过程中,由Java Object转变为Flutter Object都会经过两个步骤:
首先进行序列化,生成Flutter通道所支持的数据类型String和Uint8List类型。
Flutter端接受数据后,采用各自的方式进行反序列化生成对应Object。
理论上体积越小,序列化和反序列化效率越高,传输性能就会越好。
android和H5传输通道
从图中可以看出:
Protocol Buffers和Flat Buffers在传输中,会做一些额外的工作,需要把二进制流转化成JsBridge支持的传输类型。收到数据后,还需要对数据做解码工作。
而Json数据在Js Engine中是可以直接使用的。
对于Protocol Buffers和Flat Buffers有一点不能忽略:前面分析两者的编解码效率是比较高效的,并且体积上都要比Json小。这一点是不是可以弥补编解码带来的额外消耗,还需要通过实际测试来看一下。
04
测试结果
数据测试柱状图:
通过柱状图结果很明显:
Android和Flutter 传输:Flat Buffers > Protocol Buffers > Json
Android/Flutter 和H5传输:Json > Flat Buffers/Protocol Buffers
测试说明:
在和H5之间传输byte数组时,因需要进行base64编码,所以测试时间也包括整个base64的编解码消耗的时间。
因Flutter目前提供的Channel通道是占用UI线程的,所以实际使用情况中,还需要考虑到这点。本测试暂不考虑UI卡顿问题。
同一VM之间调用测试,因运行时的原因,有时候会有几千条甚至上万条的差异,但至少也能达到每秒几十万条的数据量,暂不考虑这种情况。
05
关于Flat Buffers
Flat Buffers传输测试的时候需要注意一点:接收到buffer数据后,需要对每一个字段进行字段解析,因Flat Buffers是流式结构,数据是根据字段偏移量动态获取,所以不能只转化成外层对象,需要对逐个字段进行解析,这样测试才比较合理,网上部分测试Flat Buffers结论比较夸张,大概也是这个原因。
这两种情况分别测试了一下:不进行解析,也就是仅byte传输,大概能达到每秒30多万条。添加上字段解析,会下降到每秒1.1万条,还是比较明显的。
06
结 论
和H5之间进行数据传递时,因js的动态语言特性,和对Json结构的天然支持,应该优先考虑使用Json。
和Flutter之间进行数据传递时,Flat Buffers,Protocol Buffers,Json要如何选择,还要看具体场景:
Json:使用便捷,成本很低,且易于维护和测试。但是相比其他两种结构,Json的自描述能力不足,在做跨语言沟通时候,仍旧需要依靠文档和主动沟通。并且在性能和安全性方面也存在缺陷。综合对比Json适合成本较小,业务数据多变的场景。
Protocol Buffers:目前比较主流的一种选择,工具链相对也比较全面。如果遇到传输性能瓶颈时,不妨考虑使用Protocol Buffers。Protocol Buffers的压缩比也是三种结构中最高的,并且在序列化和反序列化性能方面也比较突出。适合稳定或数据交互不频繁的场景。
关于测试过程中可读性的问题,花椒团队做了对应的Web版本的抓包工具,从而解决了测试友好度的问题。Flat Buffers:在序列化和反序列化方面有很大的优势。目前主要是Facebook和Cocos2d-x类应用在使用,比较适合游戏开发场景。缺点也很明显,体积上比Protocol Buffers要大一些。在数据创建时,比Protocol Buffers还要繁琐,后期维护也相对的麻烦一些。并且在不同平台上也有所差异化,考虑到国内应用的生态环境原因,所以很多时候也并非首选。
从业务前期来看Json是不错的选择。长远来看可以考虑使用Protocol Buffers。目前市面上的稳定业务也都趋向于用Protocol Buffers来替代Json。
其他测试数据
测试设备是Android9.0系统。
参考文献
https://flutter.dev/docs/development/platform-integration/platform-channels
http://google.github.io/flatbuffers/flatbuffers_benchmarks.html
https://code.fb.com/android/improving-facebook-s-performance-on-android-with-flatbuffers/
https://developers.google.com/protocol-buffers/docs/proto3
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/
界世的你当不
只做你的肩膀
无
技术干货|一手资讯|精彩活动
空·
更多精彩内容“阅读原文”
右边给我一朵小花花
以上是关于移动端混合开发,常用数据结构传输能力对比分析的主要内容,如果未能解决你的问题,请参考以下文章