0.6 性能大幅下降 [Rust 2018 1.34.2 stable]
vayn opened this issue · 9 comments
0.5 版本 README 测试运行时间0.5s
0.6 版本 README 测试运行时间35s
性能退化出现在 e0740d2 的 integer_hasher.rs#L33
换到 nightly 之后性能正常。
看来如果用 stable 没什么好的解法,要么就得把 BuildHasherDefault 注释掉。
经过在 Rust Telegram 群讨论,建议使用 rust-phf 的 hashmap 来提升性能
@vayn 感谢反馈!测试了一下确实性能下降特别严重,麻烦先用 0.5 版本,我先 revert 一下 #27 然后后面发个新版本。 cc @LuoZijun @hanabi1224
好的,已经退回前一版本了
@LuoZijun 我测试用的代码:
$ cat src/main.rs
extern crate pinyin;
pub fn main() {
let hans = "**人";
let mut args = pinyin::Args::new();
// 默认输出 [["zhong"] ["guo"] ["ren"]]
println!("{:?}", pinyin::pinyin(hans, &args));
// 包含声调 [["zh\u{14d}ng"], ["gu\u{f3}"], ["r\u{e9}n"]]
args.style = pinyin::Style::Tone;
println!("{:?}", pinyin::pinyin(hans, &args));
// 声调用数字表示 [["zho1ng"] ["guo2"] ["re2n"]]
args.style = pinyin::Style::Tone2;
println!("{:?}", pinyin::pinyin(hans, &args));
// 开启多音字模式
args = pinyin::Args::new();
args.heteronym = true;
// [["zhong"] ["guo"] ["ren"]]
println!("{:?}", pinyin::pinyin(hans, &args));
// [["zho1ng", "zho4ng"] ["guo2"] ["re2n"]]
args.style = pinyin::Style::Tone2;
println!("{:?}", pinyin::pinyin(hans, &args));
// ["zho1ng", "guo2", "re2n"]
println!("{:?}", pinyin::lazy_pinyin(hans, &args));
}
pinyin = "0.5"
:
$ time ./target/debug/_t
[["zhong"], ["guo"], ["ren"]]
[["zhōng"], ["guó"], ["rén"]]
[["zho1ng"], ["guo2"], ["re2n"]]
[["zhong", "zhong"], ["guo"], ["ren"]]
[["zho1ng", "zho4ng"], ["guo2"], ["re2n"]]
["zho1ng", "guo2", "re2n"]
real 0m0.011s
user 0m0.002s
sys 0m0.004s
pinyin = "0.6"
:
$ time ./target/debug/_t
[["zhong"], ["guo"], ["ren"]]
[["zhōng"], ["guó"], ["rén"]]
[["zho1ng"], ["guo2"], ["re2n"]]
[["zhong"], ["guo"], ["ren"]]
[["zho1ng", "zho4ng"], ["guo2"], ["re2n"]]
["zho1ng", "guo2", "re2n"]
real 0m27.724s
user 0m27.353s
sys 0m0.173s
基于 HashMap 的版本之所以出现查询慢的问题是因为 HashMap 的算法是基于运行时的,这意味着在程序首次运行的时候,程序会开始构建 HashMap 的数据结构,这个过程并不是在编译时完成的。
相关代码: e0740d2#diff-406f6e2f8fed8b53cd4b57af84a4a5b5R6
当然第二次以及后面的运行,就不再需要这个构建开销了。这个是一次性开销。
另外,为什么在 Rust 稳定版里面这个一次性的构建开销要远比 Rust 每日构建版 的开销要大呢?
这是因为标准库的 HashMap 的算法不同导致的,每日构建版里面的 HashMap 算法已经更改(正是为了解决这个问题)。至于这两种算法的优缺点这里就不展开了。
参考:
https://doc.rust-lang.org/std/collections/struct.HashMap.html
https://doc.rust-lang.org/nightly/std/collections/struct.HashMap.html
https://github.com/rust-lang/hashbrown
测试代码:
Cargo.toml:
[package]
name = "test_pinyin"
version = "0.1.0"
authors = ["luozijun <luozijun.assistant@gmail.com>"]
edition = "2018"
[dependencies]
pinyin05 = { version = "0.5", package = "pinyin" }
pinyin06 = { version = "0.6", package = "pinyin" }
src/main.rs
extern crate pinyin05;
extern crate pinyin06;
use std::time::Instant;
fn test_version_05(text: &str) {
println!("Version 05:");
let mut args = pinyin05::Args::new();
pinyin05::pinyin(text, &args);
args.style = pinyin05::Style::Tone;
pinyin05::pinyin(text, &args);
args.style = pinyin05::Style::Tone2;
pinyin05::pinyin(text, &args);
args = pinyin05::Args::new();
args.heteronym = true;
pinyin05::pinyin(text, &args);
args.style = pinyin05::Style::Tone2;
pinyin05::pinyin(text, &args);
pinyin05::lazy_pinyin(text, &args);
}
fn test_version_06(text: &str) {
println!("Version 06:");
let mut args = pinyin06::Args::new();
pinyin06::pinyin(text, &args);
args.style = pinyin06::Style::Tone;
pinyin06::pinyin(text, &args);
args.style = pinyin06::Style::Tone2;
pinyin06::pinyin(text, &args);
args = pinyin06::Args::new();
args.heteronym = true;
pinyin06::pinyin(text, &args);
args.style = pinyin06::Style::Tone2;
pinyin06::pinyin(text, &args);
pinyin06::lazy_pinyin(text, &args);
}
fn main() {
let text = "
重印前记《围城》一九四七年在上海初版,一九四八年再版,一九四九年三版,以后国内没有重印过。
偶然碰见它的新版,那都是香港的“盗印”本。没有看到**的“盗印”,据说在那里它是禁书。
美国哥伦比亚大学夏志清教授的英文著作里对它作了过高的评价,导致了一些西方语言的译本。
日本京都大学荒井健教授很久以前就通知我他要翻译,近年来也陆续在刊物上发表了译文。
现在,人民文学出版社建议重新排印,以便原著在国内较易找着,我感到意外和忻辛。
我写完《围城》,就对它不很满意。出版了我现在更不满意的一本文学批评以后,我抽空又长篇小说,命名《百合心》,
也脱胎于法文成语(Iecoeurd“artichaut),中心人物是一个女角。大约已写成了两万字。
一九四九年夏天,全家从上海迁居北京,手忙脚乱中,我把一叠看来像乱纸的草稿扔到不知哪里去了。
兴致大扫,一直没有再鼓起来,倒也从此省心省事。
年复一年,创作的冲动随年衰减,创作的能力逐渐消失——也许两者根本上是一回事,
我们常把自己的写作冲动误认为自己的写作才能,自以为要写就意味着会写。
相传幸运女神偏向着年轻小伙子,料想文艺女神也不会喜欢老头儿的;不用说有些例外,
而有例外正因为有公例。我慢慢地从省心进而收心,不作再写小说的打算。事隔三十余年,
我也记不清楚当时腹稿里的人物和情节。
就是追忆清楚了,也还算不得数,因为开得出菜单并不等于摆得成酒席,要不然,
谁都可以马上称为善做菜的名厨师又兼大请客的阔东道主了,
秉承曹雪芹遗志而拟定”后四十回“提纲的学者们也就可以凑得成和的得上一个或半个高鹗了。
剩下来的只是一个顽固的信念:假如《百合心》写得成,它会比《围城》好一点。
事情没有做成的人老有这类根据不充分的信念;我们对采摘不到的葡萄,
不但想像它酸,也很可能想像它是分外地甜。
";
let now = Instant::now();
test_version_05(text);
let elapsed = now.elapsed();
println!("Duration: {} milliseconds or {} microseconds\n",
elapsed.as_millis(),
elapsed.as_micros());
// 注: 06 版本 由于采用了基于运行时的 HashMap
// 所以第一次运行时会把 Slice 数据复制进 HashMap 中,这个过程需要一些时间。
// 但是这个复制过程只会在首次运行时发生。
println!("NOTE: 第一次运行会有初始化的过程,所以速度较慢。");
let now = Instant::now();
test_version_06(text);
let elapsed = now.elapsed();
println!("Duration: {} milliseconds or {} microseconds\n",
elapsed.as_millis(),
elapsed.as_micros());
println!("NOTE: 第二次运行不再需要初始化,所以速度较快。");
let now = Instant::now();
test_version_06(text);
let elapsed = now.elapsed();
println!("Duration: {} milliseconds or {} microseconds\n",
elapsed.as_millis(),
elapsed.as_micros());
println!("NOTE: 第三次运行不再需要初始化,所以速度较快。");
let now = Instant::now();
test_version_06(text);
let elapsed = now.elapsed();
println!("Duration: {} milliseconds or {} microseconds\n",
elapsed.as_millis(),
elapsed.as_micros());
}
Output:
cargo run
Version 05:
Duration: 58 milliseconds or 58469 microseconds
NOTE: 第一次运行会有初始化的过程,所以速度较慢。
Version 06:
Duration: 1020 milliseconds or 1020688 microseconds
NOTE: 第二次运行不再需要初始化,所以速度较快。
Version 06:
Duration: 93 milliseconds or 93273 microseconds
NOTE: 第三次运行不再需要初始化,所以速度较快。
Version 06:
Duration: 102 milliseconds or 102822 microseconds
cargo run --release
Version 05:
Duration: 8 milliseconds or 8455 microseconds
NOTE: 第一次运行会有初始化的过程,所以速度较慢。
Version 06:
Duration: 27 milliseconds or 27782 microseconds
NOTE: 第二次运行不再需要初始化,所以速度较快。
Version 06:
Duration: 10 milliseconds or 10894 microseconds
NOTE: 第三次运行不再需要初始化,所以速度较快。
Version 06:
Duration: 12 milliseconds or 12141 microseconds
上面提到了原因,那么如果要解决 稳定版的 速度问题,可选项就是在稳定版里面采用 每日构建版的 HashMap 算法,也就是 https://docs.rs/hashbrown
@mozillazg @hanabi1224 不知道你们怎么看?
@LuoZijun 我用来测试的就是 @mozillazg 的代码。现在除非预热并常驻,否则运行时间是无法接受的。不地作为一个工具库,很难限定使用者的运行场景。