跳转至内容
Xdea
创造精彩
XdeaXdea
  • 主页
  • 技术日志
  • 线下活动
Search:
Github page opens in new window
登录
  • 主页
  • 技术日志
  • 线下活动

Flutter Platform Channel 和 FFI 通道性能测试

您在这里:
  1. 首页
  2. 技术日志
  3. Flutt…

Flutter Platform Channel

Flutter 官方提供了 Platform Channel 用于 Dart 侧与平台侧的数据通信,其主要数据链路如下。 

MessageCodec 对数据进行编码,生成 Uint8List (Dart 对象,第一次拷贝)
-> 通过 native extension 将数据传给 Flutter Engine
-> 拆箱,获得 uint8_t* 的原始数据指针
-> 将数据复制到 std::vector<uint8_t> 中 (第二次拷贝)
-> 将数据 从UIRunner 传递到 PaltformRunner 中
Android 下平台链路如下
-> 创建 jbytearray 
-> 将数据拷贝到 jbytearray 中(第三次拷贝) 
-> JNI 传回,MessageCodec 解码(第四次拷贝) 
-> 由 MethodCallHandler 处理

这种方式提供了一个相对不错的性能,能够满足大部分场景下的数据传递要求。但是从实现上看仍然存在一定的瓶颈(编解码,拷贝)。将不同长度的数据通过 MethodChannel 传递给 Android 原生后再原样传回,统计往返时间以度量性能情况,结果如下。 

// Dart
var ts = DateTime.now();
for (var i = 0; i < count; i++) {
  var cal = codec.encodeMethodCall(MethodCall('test', list));
  codec.decodeMethodCall(cal);
}
print('编解码 ${DateTime.now().difference(ts).inMicroseconds}');

ts = DateTime.now();
for (var i = 0; i < count; i++) await platform.invokeMethod('benchmark', list);
print('Method Channel 调用 ${DateTime.now().difference(ts).inMicroseconds}'); 
// Kotlin
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
        .setMethodCallHandler { call, result ->
            result.success(call.arguments)
        } 
重复次数 数据长度(字节) 总编解码耗时(微秒) 总耗时(微秒) 单程通道耗时 
10 1M 4025 58894 2743 μs / MB 
20 1M 8036 90813 2069 μs / MB 
30 1M 11789 119261 1791 μs / MB 
20 512 K 4143 43645 1975 μs / MB 
20 1 M 8057 109885 2545 μs / MB 
20 5 M 79848 360039 1400 μs / MB 
20 10 M 145318 746307 1502 μs / MB 
20 20 M 319748 1402876 1353 μs / MB 
20 40 M 662206 2373947 1069 μs / MB 

从测试中可以看出,Platform Channel 的效率随着数据量大增大而提升,最终能够达到 1000 μs / MB 左右,由于没有考虑到平台侧的编解码耗时,这个实际效率应高于测试得到的效率。对于大量碎片化的小数据,Platform Channel 的效率较为低下。在传递图片等大数据量场景下,通道仍然会造成毫秒级的延迟,而当传输图片列表时,延迟会累加到上百毫秒甚至秒级,造成视觉上的卡顿,影响使用体验。 

FFI 通道

FFI 是 Dart 2.7 引入的特性,它使 Dart VM 能够直接绑定和调用 C 函数。由于静态绑定和类型完备的特性,其理论性能应当优于官方通道的 native extension 实现。使用 FFI 模拟官方的 method channel 进行数据传递,测试结果如下。

// Dart
ts = DateTime.now();
for (var i = 0; i < count; i++) {
  var ptr = allocate<Uint8>(count: message.length);
  ptr.asTypedList(message.length).setAll(0, message); 
  var retPtr = nativeFunc(message.length, ptr);
  var ret = retPtr.asTypedList(length);
  free(ptr);
}
print('FFI 双向耗时 ${DateTime.now().difference(ts).inMicroseconds}'); 
// C++
uint8_t * NativeFunc(uint64_t length, uint8_t *data) {
  …
  JNIEnv *env = getEnv();
  jbyteArray arr = env->NewByteArray(length);
  env->SetByteArrayRegion(arr, 0, length, (jbyte *)data);
  jobject obj = env->CallStaticObjectMethod(cls, mtd, arr);
  jboolean copy = JNI_FALSE;
  jbyte *ret = env->GetByteArrayElements(static_cast<jbyteArray>(obj), &copy);
  return (uint8_t *)ret;
} 
// Kotlin
fun replyBytes(arr : ByteArray): ByteArray {
    return arr
} 
重复次数数据长度(字节)总拷贝耗时(微秒)总耗时(微秒)单程通道耗时
20512K2591129574183 μs / MB
201M5086660182233 μs / MB
205M255877342361432 μs / MB

可以看出,FFI 通道的通道性能要远高于官方的 Platform Channel,效率随数据量的增大而降低。但是,由于使用 FFI 通道发送数据需在 Dart 分配 C 堆的内存空间并拷贝数据,相比于官方的在 C++ 层做拷贝的方式,效率大幅降低,造成了通道性能的大幅损失,使其最终结果已经接近官方的 Platform Channel。

Category: 技术日志ctrysbita2020年11月21日评论

作者: ctrysbita

https://www.xdea.xyz

文章导航

历史的文章历史的文章:解决 Ubuntu 安装 WPS 后无法启动的问题未来的文章未来的文章:Flutter Native Channel 设计与实现

Related posts

米家LED灯泡蓝牙MESH版拆解
2021年6月26日
双系统共享蓝牙配对实现鼠标无缝切换
2021年2月1日
Flutter Native Channel 设计与实现
2021年1月26日
解决 Ubuntu 安装 WPS 后无法启动的问题
2019年6月7日
为 Flutter 应用添加 Sentry 异常收集
2018年12月20日

发表回复 取消回复

你的电子邮件地址不会被公开 * 为必填字段

提交评论

近期文章
  • Xdea Privacy Policy
  • 米家LED灯泡蓝牙MESH版拆解
  • 双系统共享蓝牙配对实现鼠标无缝切换
  • Flutter Native Channel 设计与实现
  • Flutter Platform Channel 和 FFI 通道性能测试
近期评论
    Xdea
    © 2013-2021 Xdea Team. All Rights Reserved. | 闽ICP备18003472号