0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

更快的tsv解析代码分享

jf_wN0SrCdH 来源:Rust语言中文社区 2023-12-29 09:45 次阅读

最近在B站冲浪时发现一个 Rust 和 Go 解析 tsv 文件的视频, 作者需要解析使用get-NetTCPConnection | Format-Table -Property LocalAddress,LocalPort,RemoteAddress,RemotePort,State,OwningProcess获取的本地所有 TCP 连接信息, 文件输出大致如下

LocalAddressLocalPortRemoteAddressRemotePortStateOwningProcess
--------------------------------------------------------------
192.168.1.454339104.210.1.98443Established4504

视频作者使用 regex 正则库处理输出, 发现比 Go 版本慢, 优化后虽然比 Go 快, 但并没有领先多少, 于是我自己尝试使用别的优化方法, 解析耗时能优化使用正则解析的 10% 左右. 下面来看看我的优化过程.

•更快的 tsv 解析[1]

•项目搭建[2]

•regex 解析[3]

•减少内存分配[4]

•使用 ascii 正则[5]

•抛弃 regex[6]

•手写解析状态机[7]

SIMD 加速?[8]

•总结[9]

项目搭建

进行性能时建议使用criterion[10], 它帮我们解决了性能的内存预加载, 操作耗时, 性能记录, 图表输出等功能.

cargonew--libtsv
cdtsv
cargoaddcriterion--dev-Fhtml_reports
cargoaddregex

然后在 Cargo.toml 里添加如下bench 文件

[[bench]]
name="parse"
harness=false
//benches/parse.rs
#![allow(dead_code)]
usecriterion::{black_box,criterion_group,criterion_main,Criterion};

constOUTPUT:&str=include_str!("net.tsv");

fncriterion_benchmark(c:&mutCriterion){
todo!()
}

criterion_group!(benches,criterion_benchmark);
criterion_main!(benches);

测试使用的 tsv 一共 380 行.

regex 解析

使用正则解析的正则表达式很简单, 这里直接给代码, 为了避免重复编译正则表达式和重新分配内存报错结果列表, 这里将她们作为参数传给解析函数.

structOwnedRecord{
local_addr:String,
local_port:u16,
remote_addr:String,
remote_port:u16,
state:String,
pid:u64,
}
fnregex_owned(input:&str,re:®ex::Regex,result:&mutVec){
input.lines().for_each(|line|{
ifletSome(item)=re.captures(line).and_then(|captures|{
let(_,[local_addr,local_port,remote_addr,remote_port,state,pid])=
captures.extract();
letret=OwnedRecord{
local_addr:local_addr.to_string(),
local_port:local_port.parse().ok()?,
remote_addr:remote_addr.to_string(),
remote_port:remote_port.parse().ok()?,
state:state.to_string(),
pid:pid.parse().ok()?,
};
Some(ret)
}){
result.push(item);
}
});
assert_eq!(result.len(),377);
}

parse.rs 文件里要加上使用的正则和提前创建好列表, 并且将函数添加的 bench 目标里

fncriterion_benchmark(c:&mutCriterion){
letre=regex::new(r"(S+)s+(d+)s+(S+)s+(d+)s+(S+)s+(d+)").unwrap();
letmutr1=Vec::with_capacity(400);
c.bench_function("regex_owned",|b|{
b.iter(||{
//重置输出vector
r1.clear();
regex_owned(black_box(OUTPUT),&re,&mutr1);
})
});
}

接着跑cargo bench --bench parse进行测试, 在我的电脑上测得每次运行耗时 450 µs 左右.

减少内存分配

一个最简单的优化是使用&str以减少每次创建String带来的内存分配和数据复制.

structRecord<'a>{
local_addr:&'astr,
local_port:u16,
remote_addr:&'astr,
remote_port:u16,
state:&'astr,
pid:u64,
}

两个函数代码差不多, 所以这里不再列出来, 可以通过gits: tsv 解析[11]获取完整代码.

可惜这次改动带来的优化非常小, 在我的电脑上反复测量, 这个版本耗时在 440 µs 左右.

使用 ascii 正则

rust 的 regex 正则默认使用 unicode, 相比于 ascii 编码, unicode 更复杂, 因此性能也相对较低, 刚好要解析的内容都是ascii字符, 使用 ascii 正则是否能提升解析速度呢? regex 有regex::bytes模块用于 ascii 解析, 但为了适配字段, 这里不得不使用transmute将&[u8]强制转换成&str

fncast(data:&[u8])->&str{
unsafe{std::transmute(data)}
}
fnregex_ascii<'a>(input:&'astr,re:®ex::Regex,result:&mutVec>){
input.lines().for_each(|line|{
ifletSome(item)=re.captures(line.as_bytes()).and_then(|captures|{
let(_,[local_addr,local_port,remote_addr,remote_port,state,pid])=
captures.extract();
letret=Record{
local_addr:cast(local_addr),
local_port:cast(local_port).parse().ok()?,
remote_addr:cast(remote_addr),
remote_port:cast(remote_port).parse().ok()?,
state:cast(state),
pid:cast(pid).parse().ok()?,
};
Some(ret)
}){
result.push(item);
}
});
assert_eq!(result.len(),377);
}

添加到 bench 后性能大概多少呢?, 很遗憾, 性能与 regex_borrow 差不多, 在 430 µs 左右.

抛弃 regex

鉴于内容格式比较简单, 如果只使用 rust 内置的 split 等方法解析性能会不会更好呢? 解析思路很简单, 使用lines得到一个逐行迭代器, 然后对每行使用 split 切分空格再逐个解析即可

fnsplit<'a>(input:&'astr,result:&mutVec>){
input
.lines()
.filter_map(|line|{
letmutiter=line.split(['','	','
']).filter(|c|!c.is_empty());
letlocal_addr=iter.next()?;
letlocal_port:u16=iter.next()?.parse().ok()?;
letremote_addr=iter.next()?;
letremote_port:u16=iter.next()?.parse().ok()?;
letstate=iter.next()?;
letpid:u64=iter.next()?.parse().ok()?;
Some(Record{
local_addr,
local_port,
remote_addr,
remote_port,
state,
pid,
})
})
.for_each(|item|result.push(item));
assert_eq!(result.len(),377);
}

注意line.split只后还需要过滤不是空白的字符串, 这是因为字符串"a b"split 之后得到["a", "", "b"].

经测试, 这个版本测试耗时大概为 53 µs, 这真是一个巨大提升, rust 的 regex 性能确实有些问题.

每次 split 之后还需要 filter 感觉有些拖沓, 刚好有个split_whitespace[12], 换用这个方法, 将新的解析方法命名为split_whitespace后再测试下性能

letmutiter=line.split_whitespace();

令人意想不到的是性能居然倒退了, 这次耗时大概 60 µs, 仔细研究下来还是 unicode 的问题, 改用 ascii 版本的split_ascii_whitespace之后性能提升到 45 µs.

手写解析状态机

除了上述的方法, 我还尝试将 Record 的 local_addr 和 remote_addr 改成std::IpAddr, 消除next()?.parse().ok()?等其他方法, 但收益几乎没有, 唯一有作用的办法是手写解析状态机.

大致思路是, 对于输出来说, 我们只关系它是以下三种情况

1.换行符 NL

2.除了换行符的空白符 WS

3.非空白字符 CH

只解析 LocalAddr 和 LocalPort 解析状态机如下, 如果要解析更多字段, 按顺序添加即可.

094b06a8-a585-11ee-8b88-92fbcf53809c.png

因为代码有些复杂, 所以这里不再贴出来, 完整代码在 gits 上. 手写状态机的版本耗时大概在 32 µs 左右. 这版本主要性能提升来自手写状态机减少了循环内的分支判断.

SIMD 加速?

在上面手写解析的例子里, 处理过程类似与将输出作为一个 vec, 状态机作为另一个 vec, 将两个 vec 进行某种运算后输出结果, 应该能使用 simd 进行加速, 但我还没想出高效实现. 所以这里只给出可能的参考资料

1.zsv[13]使用 simd 加速的 csv 解析库

2.simd base64[14]一篇介绍使用 simd 加速 base64 解析的博客, 非常推荐

总结

rust regex 在某时候确实存在性能问题, 有时候使用简单的 split 的方法手动解析反而更简单性能也更高, 如果情况允许, 使用 ascii 版本能进一步提升性能, 如果你追求更好的性能, 手写一个状态不失为一种选择, 当然我不建议在生产上这么做. 同时我也期待有 simd 加速的例子.

审核编辑:黄飞

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • TCP
    TCP
    +关注

    关注

    8

    文章

    1353

    浏览量

    79059
  • 函数
    +关注

    关注

    3

    文章

    4329

    浏览量

    62578
  • 内存分配
    +关注

    关注

    0

    文章

    16

    浏览量

    8301

原文标题:更快的 tsv 解析

文章出处:【微信号:Rust语言中文社区,微信公众号:Rust语言中文社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    TSV工艺流程与电学特性研究

    本文报道了TSV过程的细节。还显示了可以在8-in上均匀地形成许多小的tsv(直径:6 m,深度:22 m)。通过这种TSV工艺的晶片。我们华林科纳研究了TSV的电学特性,结果表明
    发表于 06-16 14:02 3374次阅读
    <b class='flag-5'>TSV</b>工艺流程与电学特性研究

    请问有TSV6390AIDT和/或TSV6290AIDT的SPICE型号吗?

    你有TSV6390AIDT和/或TSV6290AIDT的SPICE型号吗? 谢谢, 何鲁丽 #运算放大器,香料宏模型
    发表于 08-06 14:07

    硅通孔(TSV)电镀

    硅通孔(TSV)电镀的高可靠性是高密度集成电路封装应用中的一个有吸引力的热点。本文介绍了通过优化溅射和电镀条件对完全填充TSV的改进。特别注意具有不同种子层结构的样品。这些样品是通过不同的溅射和处理
    发表于 01-09 10:19

    C++的G代码解析算法研究

    在数控威廉希尔官方网站 发展过程中,G 代码解析优劣是促进数控威廉希尔官方网站 的发展因素之一。但目前的解析算法,并不能更高效的进行解析处理。经过对G 代码进行分析,
    发表于 07-21 16:36 0次下载

    轨到轨输入/输出20 MHz的运算放大器TSV991/TSV992/TSV994

    The TSV99x and TSV99xA family of single, dual, and quad operational amplifiers offers low voltage
    发表于 09-04 14:51 12次下载
    轨到轨输入/输出20 MHz的运算放大器<b class='flag-5'>TSV</b>991/<b class='flag-5'>TSV</b>992/<b class='flag-5'>TSV</b>994

    通用输入/输出轨到轨低功耗操作放大器TSV321/TSV358/TSV324/TSV321A/TSV358A/TSV324A

    The TSV358, TSV358A, TSV324, and TSV324A (dual and quad) devices are low voltage versions of
    发表于 09-05 09:12 6次下载
    通用输入/输出轨到轨低功耗操作放大器<b class='flag-5'>TSV</b>321/<b class='flag-5'>TSV</b>358/<b class='flag-5'>TSV</b>324/<b class='flag-5'>TSV</b>321A/<b class='flag-5'>TSV</b>358A/<b class='flag-5'>TSV</b>324A

    微(60µ一)宽的带宽(2.4 MHz)的CMOS运算放大器TSV6390/TSV6390A/TSV6391/TSV6391A

    The TSV6390, TSV6391, and their “A” versions are single operational amplifiers (op amps) offering
    发表于 09-05 09:34 4次下载
    微(60µ一)宽的带宽(2.4 MHz)的CMOS运算放大器<b class='flag-5'>TSV</b>6390/<b class='flag-5'>TSV</b>6390A/<b class='flag-5'>TSV</b>6391/<b class='flag-5'>TSV</b>6391A

    高功因数(1.15兆赫为45微米)cmos运算放大器TSV521/TSV522/TSV524/TSV521A/TSV522A/TSV524A

    The TSV52x and TSV52xA series of operational amplifiers offer low voltage operation and rail-torail
    发表于 09-05 09:52 5次下载
    高功因数(1.15兆赫为45微米)cmos运算放大器<b class='flag-5'>TSV</b>521/<b class='flag-5'>TSV</b>522/<b class='flag-5'>TSV</b>524/<b class='flag-5'>TSV</b>521A/<b class='flag-5'>TSV</b>522A/<b class='flag-5'>TSV</b>524A

    轨到轨输入/输出60µ880千赫5V CMOS运算放大器TSV630/TSV630A/TSV631/TSV631A

    The TSV630 and TSV631 devices are single operational amplifiers offering low voltage, low power operation, and rail-to-rail input and ou
    发表于 09-05 10:04 16次下载
    轨到轨输入/输出60µ880千赫5V CMOS运算放大器<b class='flag-5'>TSV</b>630/<b class='flag-5'>TSV</b>630A/<b class='flag-5'>TSV</b>631/<b class='flag-5'>TSV</b>631A

    轨到轨输入/输出,29µ,420 kHz的CMOS运算放大器TSV62x,TSV62xA

    The TSV622, TSV622A, TSV623, TSV623A, TSV624, TSV
    发表于 09-05 10:58 4次下载
    轨到轨输入/输出,29µ,420 kHz的CMOS运算放大器<b class='flag-5'>TSV</b>62x,<b class='flag-5'>TSV</b>62xA

    轨到轨输入/输出29µ420 kHz的CMOS运算放大器TSV620,TSV620A,TSV621,TSV621A

    The TSV620, TSV620A, TSV621, and TSV621A are single operational amplifiers offering low volt
    发表于 09-05 11:01 6次下载
    轨到轨输入/输出29µ420 kHz的CMOS运算放大器<b class='flag-5'>TSV</b>620,<b class='flag-5'>TSV</b>620A,<b class='flag-5'>TSV</b>621,<b class='flag-5'>TSV</b>621A

    什么是TSV封装?TSV封装有哪些应用领域?

    硅通孔威廉希尔官方网站 (Through Silicon Via, TSV)威廉希尔官方网站 是一项高密度封装威廉希尔官方网站 ,正在逐渐取代目前工艺比较成熟的引线键合威廉希尔官方网站 ,被认为是第四代封装威廉希尔官方网站 。TSV威廉希尔官方网站 通过铜、钨、多晶硅等导电物质
    发表于 08-14 15:39 9.1w次阅读

    英特尔现代代码:您的突破性创新更快

    了解英特尔的现代代码计划如何帮助开发人员为今天和明天创建更快代码
    的头像 发表于 11-13 06:38 1433次阅读

    什么是硅或TSV通路?使用TSV的应用和优势

    TSV不仅赋予了芯片纵向维度的集成能力,而且它具有最短的电传输路径以及优异的抗干扰性能。随着摩尔定律慢慢走到尽头,半导体器件的微型化也越来越依赖于集成TSV的先进封装。
    发表于 07-25 10:09 773次阅读
    什么是硅或<b class='flag-5'>TSV</b>通路?使用<b class='flag-5'>TSV</b>的应用和优势

    单片机解析g代码的方法

    的运动。 解析G代码是将其转化为单片机能够理解和执行的指令集。单片机解析G代码的方法主要包括以下几个方面:G代码的格式
    的头像 发表于 12-22 14:15 1762次阅读