【工具箱】优化对于大文件的支持 & v1.0.18+1更新
在我自己使用工具箱的时候呢,发现一个问题,对于flutter_rust_bridge
这个库,如果说传输的内容过大的话,会触发一个panic, 因此,在之前,这个工具箱所能支持的输入的数据大小是有限制的,具体多少,我们稍后讨论。
情景复现
这里,我们先来复现一下,这个问题是怎么出现的,首先,简单的搞一个例子。
flutter_rust_bridge_codegen create my_app && cd my_app && flutter run
然后,简单的写一个例子,返回传入数组的长度。
pub fn test_vec(data: Vec<u8>) -> usize {
data.len()
}
简单的写一个调用,试一下。
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_via_create/src/rust/api/simple.dart';
import 'package:flutter_via_create/src/rust/frb_generated.dart';
Future<void> main() async {
await RustLib.init();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('flutter_rust_bridge quickstart')),
body: Center(
child: TextButton(
onPressed: () {
final result = greet(name: "world");
print(result);
testVec(data: Uint8List(1024 * 1024 * 1024 * 2)).then((value) => print(value));
},
child: const Text("data"),
),
),
),
);
}
}
然后,点击一下,就会得到一个错误。
thread '<unnamed>' panicked at library/alloc/src/raw_vec.rs:25:5:
capacity overflow
stack backtrace:
0: 0x102c107c4 - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::h243268f17d714c7f
1: 0x102c536fc - core::fmt::write::hb3cfb8a30e72d7ff
2: 0x102c067a8 - std::io::Write::write_fmt::hfb2314975de9ecf1
3: 0x102c12cd4 - std::panicking::default_hook::{{closure}}::h14c7718ccf39d316
4: 0x102c128f8 - std::panicking::default_hook::hc62e60da3be2f352
5: 0x102a071fc - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::ha3771cb704b88a16
6: 0x102a0e92c - flutter_rust_bridge::misc::panic_backtrace::PanicBacktrace::setup::{{closure}}::hf7eef2f7421ac658
7: 0x102c138f0 - std::panicking::rust_panic_with_hook::h09e8a656f11e82b2
8: 0x102c131d8 - std::panicking::begin_panic_handler::{{closure}}::h1230eb3cc91b241c
9: 0x102c10c50 - std::sys::backtrace::__rust_end_short_backtrace::hc3491307aceda2c2
10: 0x102c12ec8 - _rust_begin_unwind
11: 0x102c76534 - core::panicking::panic_fmt::ha4b80a05b9fff47a
12: 0x102c450e4 - alloc::raw_vec::capacity_overflow::h88d6b8d70532516f
13: 0x102c760cc - alloc::raw_vec::handle_error::h3874354ba7b66dcc
14: 0x102a05c68 - alloc::raw_vec::RawVec<T,A>::reserve_exact::h09bb17568e2b0d00
15: 0x102a0fd68 - alloc::vec::Vec<T,A>::reserve_exact::h025a1c8279936d8b
16: 0x102a08d2c - flutter_rust_bridge::ffi_binding::vec_resize::h02bf0f6657da6c5a
17: 0x102a08c64 - _frb_rust_vec_u8_resize
thread '<unnamed>' panicked at library/core/src/panicking.rs:221:5:
panic in a function that cannot unwind
stack backtrace:
0: 0x102c107c4 - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::h243268f17d714c7f
1: 0x102c536fc - core::fmt::write::hb3cfb8a30e72d7ff
2: 0x102c067a8 - std::io::Write::write_fmt::hfb2314975de9ecf1
3: 0x102c12cd4 - std::panicking::default_hook::{{closure}}::h14c7718ccf39d316
4: 0x102c128f8 - std::panicking::default_hook::hc62e60da3be2f352
5: 0x102a071fc - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::ha3771cb704b88a16
6: 0x102a0e92c - flutter_rust_bridge::misc::panic_backtrace::PanicBacktrace::setup::{{closure}}::hf7eef2f7421ac658
7: 0x102c138f0 - std::panicking::rust_panic_with_hook::h09e8a656f11e82b2
8: 0x102c131d8 - std::panicking::begin_panic_handler::{{closure}}::h1230eb3cc91b241c
9: 0x102c10c50 - std::sys::backtrace::__rust_end_short_backtrace::hc3491307aceda2c2
10: 0x102c12ec8 - _rust_begin_unwind
11: 0x102c76564 - core::panicking::panic_nounwind_fmt::h91ee161184879b56
12: 0x102c765dc - core::panicking::panic_nounwind::heab7ebe7a6cd845c
13: 0x102c76754 - core::panicking::panic_cannot_unwind::hedc43d82620205bf
14: 0x102a08c58 - _frb_rust_vec_u8_resize
thread caused non-unwinding panic. aborting.
然后,本着,看着这个作者非常活跃,所以提一个issue问一下[2],这里非常感谢作者,提供一个这么好用的库,但是,经过一顿讨论,我最终换了方案,这个稍后在讨论,我具体怎么做的。
虽然,我换了方案,但是呢,这个问题,我其实知道是什么原因,这里简单说一下,并且,给出我换方案的具体理由。
我们,根据日志,定位到,出错的代码位置,
// frb_rust/src/ffi_binding/mod.rs
fn vec_resize(vec: &mut Vec<u8>, new_len: i32) {
let new_len = new_len as usize;
if new_len > vec.len() {
vec.reserve_exact(new_len - vec.len());
}
vec.resize(new_len, 0);
// This will stop the whole generator and tell the users, so we do not care about testing it
// frb-coverage:ignore-start
debug_assert!(vec.len() == new_len);
// frb-coverage:ignore-end
}
然后,我们发现,他的新长度用的是i32的类型,那么这里会发生什么呢,我们先来看一段rust的代码。
fn main() {
let mut vec1 = vec![0; 2 * 1024 * 1024 * 1024];
println!("{}", vec1.capacity());
vec1.resize(2 * 1024 * 1024 * 1024 + 0x10, 0);
println!("{}", vec1.capacity());
}
这个代码,会输出
2147483648
4294967296
最后,resize的值,会直接超过i32的,因此这里发生了问题。好了,这其实不重要,只是简单的说一下,具体为什么这里会报错。
然后,决定换方案,的最重要原因,是因为,这个传输的过程会锁住UI, 导致页面的卡顿,而且,对于类似于网页、Android、iOS等设备,这个表现就是直接卡死,因此,我们不能一次传输过大的数据,因此我们需要思考新的解决方案。
解决方案
因为,我们不能一次,传输过多的数据,这里,要解决的方案也非常简单,我们只需要分段传输就好了,不直接传递整段的数据,这里,我们只需要做一个包装类。
#[frb(opaque)]
#[derive(Clone)]
pub struct DataWrapper {
data: Vec<u8>,
pub size: usize,
}
impl DataWrapper {
pub fn new() -> Self {
Self { data: vec![], size: 0 }
}
pub(crate) fn from(data: Vec<u8>) -> Self {
let size = data.len();
Self { data, size }
}
pub fn add(&mut self, data: Vec<u8>) {
self.data.extend(data);
self.size = self.data.len();
}
pub fn seek(&self, begin: usize, end: usize) -> Vec<u8> {
if begin >= end {
return vec![];
}
if end > self.size {
return self.data[begin..self.size].to_vec();
}
self.data[begin..end].to_vec()
}
pub(crate) fn get_data(&self) -> &Vec<u8> {
&self.data
}
}
注意,这里只开放了add
函数,以及seek
函数,其他的话,如果传输过大的数据,是不能直接调用的,直接调用的话,也会得到同样的异常。
然后,我们只需分别实现,创建和读取的函数就可以了。
Future<DataWrapper> convertListToDataWrapper(List<int> data) async {
DataWrapper wrapper = await DataWrapper.newInstance();
int groupCount = data.length ~/ kBlockSize;
for (int i = 0; i < groupCount; i++) {
await wrapper.add(data: data.sublist(i * kBlockSize, (i + 1) * kBlockSize));
}
if (data.length % kBlockSize != 0) {
await wrapper.add(data: data.sublist(groupCount * kBlockSize));
}
return wrapper;
}
Future<Uint8List> convertWrapperToList(DataWrapper wrapper) async {
var result = Uint8List(wrapper.size.toInt());
int groupCount = wrapper.size.toInt() ~/ kBlockSize;
for (int i = 0; i < groupCount; i++) {
var chunk = await wrapper.seek(begin: BigInt.from(i * kBlockSize), end: BigInt.from((i + 1) * kBlockSize));
result.setAll(i * kBlockSize, chunk);
}
if (wrapper.size.toInt() % kBlockSize != 0) {
var chunk = await wrapper.seek(begin: BigInt.from(groupCount * kBlockSize), end: wrapper.size);
result.setAll(groupCount * kBlockSize, chunk);
}
return result;
}
这里,简单的实现一下,有同样需求的读者可以参考一下。
效果实战
这样,其实,我的工具箱,就支持,任意大小的文件了,这里,我们简单的创建一个50g的文件,来测试一下。
dd if=/dev/urandom of=random_file_50g.bin bs=1M count=50000
这里,计算的时间有些长,如果想测试的读者,可能需要等好久。
目前,运行过程的内存占用还是比较稳定的,即使是,超大的文件,也不会占用过多的内存,目前,应该没有多少可视化的工具,支持这么大的文件的计算,目前测试还是在debug下测试的,release占用的资源会更少。
这里,和openssl对比一下,结果是正确的。
通过测试,可以发现,我们可以轻松的计算这个哈希值的,不会出现什么错误,针对于哈希函数,我这做了分块的处理,本身内存占用也不会太大。对于其他函数,比如加密,不建议加密如此大的文件,因为这个结果和输入基本上是等长的,如果处理超大文件,结果展示会占用太多内存,当然,电脑内存大的读者,可以自行取舍。
其他优化
文件导入的优化
针对于文件的使用场景,这里做了一定的优化,目前,可以直接拖拽进面板
这里,如果文件过大,目前Hex View只会展示前10m的数据。
如果想要查看hex内容,建议使用hex的编辑器,比如ImHex, 010Editor之类的,不建议采用本工具来查看内容。
过程导出的优化
这里,优化了过程导出的数据结构,会导致,原来导出的内容,可能无法导入到新的,请注意。然后,导出的结构,也是可以直接拖拽到对应的面板的。
优化子过程的展示
目前,添加了子过程,没有办法直观的发现,是否存在子过程,因此这里简单的优化了一下,目前没有子过程的时候,这里是灰色的。
添加了子过程之后呢,
这里,会变成对应的主题色。
子窗口数据同步
虽然,我们支持了并行计算,但是,目前并行计算的展示并不优雅,只是以字符串的形式进行了展示,如果相同时查看多个过程的结果对比就非常不容易,因此这里在打开子窗口之后,我们加入了数据同步的功能,开启会后,主窗口的输入内容,会自动的同步到所有子窗口。
点击,按钮开始同步功能。
方便程度再次升级。
总结
本次,工具箱的更新,主要是为了解决大文件传输的问题,然后,捎带着优化了一波其他的内容,在使用过程当中,发现的其他问题,也可以联系我,好了,快乐的时间过得特别快,又到了说再见的时候了,咱们下次再见。
下载地址
链接: https://pan.baidu.com/s/1vD3ixN63Hr23bp2Sagg7UQ?pwd=8h28 提取码: 8h28
参考资料
-
https://github.com/fzyzcjy/flutter_rust_bridge/ -
https://github.com/fzyzcjy/flutter_rust_bridge/issues/2404
原文始发于微信公众号(Coder小Q):【工具箱】优化对于大文件的支持 & v1.0.18+1更新
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论