标题: [挑战]100G文本统计+排序 [打印本页]
作者: 523066680 时间: 2019-1-29 19:07 标题: [挑战]100G文本统计+排序
本帖最后由 523066680 于 2019-1-30 11:16 编辑
源自ChinaUnix一个用户提出的问题,当时帮题主理清基础的处理方式,但是上G的文件依然没有写出高效率的代码。
原帖1,先从较简短的示例描述
http://bbs.chinaunix.net/thread-4263348-1-1.html
Windows19 发表于 2017-06-20 10:54
有几百GB,LOG,乱出八粗的
找出在文本内重复字符串从多到少打印,
找出重复次数最多字符串,然后整行打印 (注: 按2种类型条件判断统计,重复字母串次数,重复数字串次数,然后由多到少整行打印出来). 剩余的行也需一并打印出来(出来结果应和原LOG行数一致)
找出两种类型字符串在文本中重复最多次数,从多到少排序
a.txt- 65425855662efssaezsdfcsf//sff.sdf/'s;f]\sDed33dds3
- 65425855662efssaezsdfcsf/ /sff.sdf/'s;f] \sDed33sdds1
- yjyjgwwwghfg56www g.tgjgcom445.5454.'55.4l5
- efssaezsdfcsf//58969752sff.sdf/'s;f]\sDed33sds0
- efssaezsdfcsf/ 58969752/sff.sdf/'s;f] \sDed33sdds5
- 65425855662efssaezsdfcsf/ /sff.sdf/'s;f] \sDed33s00000000
- 65425855662efs\saezsdf][grytryg*f-x+f5g5ty'5t;54r]\5/e.,6ftfr//www.fsfsf.com
复制代码
按条件统计后应该得到(人工审核不知道有没错)- 65425855662efssaezsdfcsf//sff.sdf/'s;f]\sDed33dds3
- 65425855662efssaezsdfcsf/ /sff.sdf/'s;f] \sDed33sdds1
- 65425855662efssaezsdfcsf/ /sff.sdf/'s;f] \sDed33s00000000
- efssaezsdfcsf//58969752sff.sdf/'s;f]\sDed33sds0
- efssaezsdfcsf/ 58969752/sff.sdf/'s;f] \sDed33sdds5
- 65425855662efs\saezsdf][grytryg*f-x+f5g5ty'5t;54r]\5/e.,6ftfr//www.fsfsf.com
- yjyjgwwwghfg56www g.tgjgcom445.5454.'55.4l5
复制代码
这个题主是个典型的令人头痛的类型,因为他不把问题描述完整,一开始还发过几个简化过的问题的帖子,等到有人回复,他又渐渐把完整的问题浮出水面(可能有些细节他自己都不确定)
我在另一个帖子对处理方案做了详细说明,题主认为符合需求,可供参考:
523066680 发表于 2017-06-22 23:12
以原贴的段落为例,
[0]65425855662efssaezsdfcsf//sff.sdf/'s;f]\sDed33dds3
[1]65425855662efssaezsdfcsf/ /sff.sdf/'s;f] \sDed33sdds1
[2]yjyjgwwwghfg56www g.tgjgcom445.5454.'55.4l5
[3]efssaezsdfcsf//58969752sff.sdf/'s;f]\sDed33sds0
[4]efssaezsdfcsf/ 58969752/sff.sdf/'s;f] \sDed33sdds5
[5]65425855662efssaezsdfcsf/ /sff.sdf/'s;f] \sDed33s00000000
[6]65425855662efs\saezsdf][grytryg*f-x+f5g5ty'5t;54r]\5/e.,6ftfr//www.fsfsf.com
处理结果:
[1]65425855662efssaezsdfcsf/ /sff.sdf/'s;f] \sDed33sdds1
[0]65425855662efssaezsdfcsf//sff.sdf/'s;f]\sDed33dds3
[5]65425855662efssaezsdfcsf/ /sff.sdf/'s;f] \sDed33s00000000
[4]efssaezsdfcsf/ 58969752/sff.sdf/'s;f] \sDed33sdds5
[3]efssaezsdfcsf//58969752sff.sdf/'s;f]\sDed33sds0
[6]65425855662efs\saezsdf][grytryg*f-x+f5g5ty'5t;54r]\5/e.,6ftfr//www.fsfsf.com
[2]yjyjgwwwghfg56www g.tgjgcom445.5454.'55.4l5
每行的关键字在全文中出现的次数统计(单行内多次计为一次)
f(6),efssaezsdfcsf(5),sff(5),s(5),sDed(5),33(5),sdf(5),65425855662(4),3(1),dds(1)
f(6),s(5),sff(5),efssaezsdfcsf(5),33(5),sdf(5),sDed(5),65425855662(4),sdds(2),1(1)
5(3),g(2),www(2),yjyjgwwwghfg(1),56(1),445(1),tgjgcom(1),4(1),l(1),5454(1),55(1)
f(6),33(5),sdf(5),sDed(5),s(5),sff(5),efssaezsdfcsf(5),58969752(2),0(1),sds(1)
f(6),efssaezsdfcsf(5),sff(5),s(5),sDed(5),33(5),sdf(5),5(3),sdds(2),58969752(2)
f(6),efssaezsdfcsf(5),s(5),sff(5),sDed(5),sdf(5),33(5),65425855662(4),00000000(1)
f(6),65425855662(4),5(3),g(2),www(2),x(1),ftfr(1),e(1),com(1),54(1),t(1),ty(1),fsfsf(1),grytryg(1),efs(1),r(1),6(1),saezsdf(1)
处理后的顺序(按行排序,从最高的频率开始对比;如果最高的次数相同,则对比第二列,以此类推)
f(6),s(5),sff(5),efssaezsdfcsf(5),33(5),sdf(5),sDed(5),65425855662(4),sdds(2),1(1)
f(6),efssaezsdfcsf(5),sff(5),s(5),sDed(5),33(5),sdf(5),65425855662(4),3(1),dds(1)
f(6),efssaezsdfcsf(5),s(5),sff(5),sDed(5),sdf(5),33(5),65425855662(4),00000000(1)
f(6),efssaezsdfcsf(5),sff(5),s(5),sDed(5),33(5),sdf(5),5(3),sdds(2),58969752(2)
f(6),33(5),sdf(5),sDed(5),s(5),sff(5),efssaezsdfcsf(5),58969752(2),0(1),sds(1)
f(6),65425855662(4),5(3),g(2),www(2),x(1),ftfr(1),e(1),com(1),54(1),t(1),ty(1),fsfsf(1),grytryg(1),efs(1),r(1),6(1),saezsdf(1)
5(3),g(2),www(2),yjyjgwwwghfg(1),56(1),445(1),tgjgcom(1),4(1),l(1),5454(1),55(1)
纯数字,前后对比:
Before:
[0]6,5,5,5,5,5,5,4,1,1
[1]6,5,5,5,5,5,5,4,2,1
[2]3,2,2,1,1,1,1,1,1,1,1
[3]6,5,5,5,5,5,5,2,1,1
[4]6,5,5,5,5,5,5,3,2,2
[5]6,5,5,5,5,5,5,4,1
[6]6,4,3,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1
After
[1]6,5,5,5,5,5,5,4,2,1
[0]6,5,5,5,5,5,5,4,1,1
[5]6,5,5,5,5,5,5,4,1
[4]6,5,5,5,5,5,5,3,2,2
[3]6,5,5,5,5,5,5,2,1,1
[6]6,4,3,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1
[2]3,2,2,1,1,1,1,1,1,1,1
注意上面的结果是同一“单词”在单行中重复多次记为一次的。应考虑按多次计算的情况(或者做个开关允许按不同方式处理)
65425855662efssaezsdfcsf/ /sff.sdf/'s;f] \sDed33s00000000
一行内两次算是2次吗?我以为一行内多次也计为1次,还特地做了判断
Windows19 回复 53# 523066680
如果算2次影响效率
那在一行内出现相同的算1次也可以
当然这个细节不是主要问题,数据量大以后要考虑哈希表的内存占用,如果改用文件存储数据结构,则需要考虑效率、硬盘占用问题。
"分而治之"策略 和 归并排序 应该是少不了的,个人认为是很好的锻炼。
当时(17年)的代码虽然借用DB_File模块支持了大文件处理,但效率极低,正打算重写。
为了减少过度的测试时间和硬盘消耗,将此问题稍作修改,改为10G文件(或1G),语言不限,大家有兴趣吗?
作者: 523066680 时间: 2019-1-29 19:26 标题: 补充 RE: [挑战]100G文本统计+排序
本帖最后由 523066680 于 2019-3-20 19:52 编辑
这个问题有一个分支剧情,就是当时我建议题主去C++板块提问,windoze版主给他提出了解答此题6000元的报价
windoze 回复 3# Windows19
nonono,问题不是支付方式,而是金额。
你要的这个东西显然已经超出论坛技术讨论的范围,我拍脑袋的估一下差不多要一周左右的工作量,假设你找了一个月薪20000的人帮你做这件事,至少就得掏5000块,而且一般零活的价钱会在月结工资的基础上再向上浮动10~20%。
不知道lz有没有做好掏6000块的心理准备。
想借windoze版主的话提醒各位一下,技术是有价值的,像lxh发的那些各种需求,十几二十元,三五人去给他轮番全方位解答,着实是在贬低技术的价值。
以及windoze为这个问题评估了一个价值,各位如果感兴趣可以自己试试,有话题,可以讨论,代码也未必要发出来(几千大洋 )。
----------------- 2019-03-19 补充 -----------------
生成随机文本的代码(初步),
文件大小通过 FILE_SIZE 指定,效率偏低,在我这里(CPU 4GHz) 生成100M需要 13秒,太慢。欢迎补充更好的生成工具。
g++ -std=c++11 -O3 -o gen gen.cpp
----------------- 2019-03-20 更新 -----------------
g++ -std=c++11 -O3 -o gen gen.cpp
本机测试 100M 1.3秒- // Generate Random Text
- // 2019-03-20
-
- #include <iostream>
- #include <fstream>
- #include <string>
- #include <random>
- #include <chrono>
- using namespace std;
- using namespace std::chrono;
-
- void init_table( string &table );
- void write_random_data( string &table, ofstream &fh );
- void time_used(system_clock::time_point& time_a);
-
- const long long FILE_SIZE = 1024*1024*100;
-
- int main(int argc, char *argv[] )
- {
- string table;
- init_table(table);
- string file = "F:/Cu_Topics/data.txt";
- ofstream fh(file, ios::binary);
- if ( fh.fail() ) {
- cout << "Can't open file" << endl;
- return 1;
- }
-
- system_clock::time_point timestamp = chrono::system_clock::now();
- write_random_data( table, fh );
- time_used( timestamp );
- return 0;
- }
-
- void write_random_data( string &table, ofstream &fh )
- {
- char *buff = new char[FILE_SIZE+1];
- default_random_engine engine(12345);
- uniform_int_distribution<int> get_rand(0, table.size()-1);
- register long long iter = 0LL;
- while ( iter < FILE_SIZE ) {
- buff[iter++] = table[ get_rand(engine) ];
- }
- fh.write( buff, FILE_SIZE );
- }
-
- void init_table( string &table )
- {
- table =
- "abcdefghijklmnopqrstuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "1234567890" "1234567890"
- " " "!(),-.:;?[]_{}" "\n\n"
- ;
- }
-
- void time_used(system_clock::time_point& time_a) {
- duration<double> diff;
- diff = chrono::system_clock::now() - time_a;
- cout << "Time used: " << diff.count() << endl;
- time_a = chrono::system_clock::now();
- }
复制代码
----------------- 2019-03-21 更新 -----------------
12楼提供了可执行的生成工具,我这里附一个小批处理,方便修改(但注意变量值范围限制)。- @echo off
- set /a MB=1^<^<20, SIZE=MB*100, SEED=2019
- gensample %SIZE% %SEED%>F:/CU_Topics/a.txt
- pause
复制代码
作者: ivor 时间: 2019-1-29 20:25
这种问题有点偏
作者: yhcfsr 时间: 2019-1-29 21:38
不管三七二十二,先mark收藏,随时关注.
我看到结果才明白,楼主说的"字符串"的划分,是纯数字[0-9]+,或纯字母[a-z]+.
作者: Batcher 时间: 2019-1-30 15:06
一周才收6000真是良心价啊,我项目里的工程师收客户至少3000每人天
作者: bailong360 时间: 2019-3-19 18:10
嘛, 几千大洋是在 100G 数据, 2G 内存, C++ 编写 这几个条件下算出的价格
数据量减小一百倍(OR 内存增大一百倍[如果有钱的话]), 同时换简单点的语言的话, 价格应该就没那么高了
那位老哥显然没有理解"量变引起质变"这句话
用 Rust 简单试了下- use lazy_static::lazy_static;
- use regex::Regex;
- use std::collections::HashMap;
- use std::fs::File;
- use std::io::{self, BufRead, BufReader, Write};
-
- /// 从字符串中提取出"字符串"
- fn get_keywords(text: &str) -> Vec<&str> {
- lazy_static! {
- static ref RE: Regex = Regex::new(r#"\d+|[a-zA-Z]+"#).unwrap();
- }
- RE.find_iter(text)
- .map(|m| &text[m.start()..m.end()])
- .collect()
- }
-
- fn main() {
- let path = "a.txt";
- let file = File::open(path).expect("无法打开文件");
- let file = BufReader::new(file);
-
- // 遍历全文, 确定每个"字符串"出现次数
- let mut keyword_map = HashMap::new();
-
- for line in file.lines() {
- let line = line.unwrap();
- for keyword in get_keywords(&line) {
- // TODO: 此处的 clone 可避免否?
- *keyword_map.entry(keyword.to_owned()).or_insert(0) += 1;
- }
- }
-
- // 再次遍历, 确定每行所包含的字符串的出现次数
- let file = BufReader::new(File::open(path).unwrap());
- let mut line_map = HashMap::new();
-
- for line in file.lines() {
- let line = line.unwrap();
- let keywords = get_keywords(&line);
-
- let mut occurs_cnt = keywords.iter().map(|&s| *keyword_map.get(s).unwrap()).collect::<Vec<_>>();
- occurs_cnt.sort_unstable_by(|a, b| b.cmp(a));
-
- line_map.entry(occurs_cnt).or_insert(vec![]).push(line);
- }
-
- // 排序
- let mut keys = line_map.keys().collect::<Vec<_>>();
- keys.sort_unstable_by(|a, b| b.cmp(a));
-
- // 输出
- let stdout = io::stdout();
- let mut handle = stdout.lock();
- for key in keys {
- let lines = line_map.get(key).unwrap();
- for line in lines {
- handle.write(line.as_bytes()).unwrap();
- handle.write(&[b'\n']).unwrap();
- }
- }
- }
复制代码
没有测试文本, 就拿1L的文本复制粘贴了100MB测试了一下.
时间 7s
内存占用 176MB
内存占用大了点, 毕竟 Rust 好像没啥成熟的外排序库, 也不想手写 (
同时 profile 了一下发现 1/3 多的时间竟然花在了正则上面...这个倒是可以考虑手写一下
作者: 523066680 时间: 2019-3-19 18:38
本帖最后由 523066680 于 2019-3-19 18:53 编辑
回复 6# bailong360
在 bathome 看到 Rust 代码,很新鲜。
最近在知乎偶然看到 CU 的那位版主(徐晨),应该 Rust 他也熟的
https://zhuanlan.zhihu.com/p/38948830
我自己的重制版还没写出来,惭愧
作者: 老刘1号 时间: 2019-3-19 19:12
mark一下,,
作者: bailong360 时间: 2019-3-19 23:29
最近在学 Rust, 感觉非常好用~
回复 7# 523066680
诶, 这位我有印象. 没想到竟然是 CU 的版主
我最终还是找了个 API 很难用的 crate 改了下, 先用起来再说
这次 100MB 用时 4.4s, 最高内存占用 200MB
看起来内存占用并没有减少, 嘛...因为文件大小太小了看不出来, 我也没耐心弄个几G的文件测试
加上多线程应该还能快一点, 不过试了下加上效果也不是很明显, 估计瓶颈应该在 IO 上 (也有可能是文件太小了...
主体代码如下, 完整代码传百度云了(论坛上传竟然要 Flash 支持?...
链接: https://pan.baidu.com/s/1lvI4UPwOclizsfp108j0EQ 提取码: s39j- use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
- use extsort::*;
- use std::collections::HashMap;
- use std::fs::File;
- use std::io::{self, BufRead, BufReader, Read, Write};
-
- /// 从字符串中提取出"字符串"
- fn get_keywords(text: &str) -> Vec<&str> {
- #[inline]
- fn type_of(b: u8) -> u8 {
- if b.is_ascii_alphabetic() {
- return 1;
- } else if b.is_ascii_digit() {
- return 2;
- } else {
- return 3;
- }
- }
-
- let bytes = text.as_bytes();
- let mut ret = vec![];
- let mut pos = 0;
- for i in 0..bytes.len() - 1 {
- if bytes[i].is_ascii_alphanumeric() && type_of(bytes[i]) != type_of(bytes[i + 1]) {
- ret.push(&text[pos..=i]);
- pos = i + 1;
- } else if !bytes[i].is_ascii_alphanumeric() {
- pos = i + 1;
- }
- }
- if bytes[pos].is_ascii_alphanumeric() && type_of(bytes[pos]) == type_of(bytes[bytes.len() - 1])
- {
- ret.push(&text[pos..]);
- }
- ret
- }
-
- /// 读取文件, 确定每个"字符串"出现的次数
- fn make_keyword_map(path: &str) -> HashMap<String, u32> {
- let file = BufReader::new(File::open(path).expect("无法打开文件"));
- let mut ret = HashMap::new();
-
- for line in file.lines() {
- let line = line.unwrap();
- for keyword in get_keywords(&line) {
- *ret.entry(keyword.to_owned()).or_insert(0) += 1;
- }
- }
-
- ret
- }
-
- /// 根据每行的字符串出现次数生成一个"特征值"
- fn make_line_map(
- path: &str,
- keyword_map: &HashMap<String, u32>,
- ) -> (usize, HashMap<Vec<u32>, Vec<u32>>) {
- let file = BufReader::new(File::open(path).expect("无法打开文件"));
- let mut line_cnt = 0;
- let mut ret = HashMap::new();
-
- for (idx, line) in file.lines().enumerate() {
- let line = line.unwrap();
- let keywords = get_keywords(&line);
-
- let mut occurs_cnt = keywords
- .iter()
- .map(|&s| *keyword_map.get(s).unwrap())
- .collect::<Vec<_>>();
- occurs_cnt.sort_unstable_by(|a, b| b.cmp(a));
-
- ret.entry(occurs_cnt).or_insert(vec![]).push(idx as u32);
- line_cnt = idx;
- }
-
- (line_cnt, ret)
- }
-
- #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
- struct Line(u32, String);
-
- impl Sortable<Line> for Line {
- fn encode(item: Line, write: &mut Write) {
- write.write_u32::<LittleEndian>(item.0).unwrap();
- write.write(item.1.as_bytes()).unwrap();
- write.write(&[b'\n']).unwrap();
- }
-
- fn decode(read: &mut Read) -> Option<Line> {
- let idx = read.read_u32::<LittleEndian>().ok()?;
- let mut bytes = read.bytes();
- let s = String::from_utf8(
- bytes
- .by_ref()
- .map(|b| b.unwrap())
- .take_while(|b| *b != b'\n')
- .collect(),
- )
- .unwrap();
- // eprintln!("{} {}", idx, s);
- Some(Line(idx, s))
- }
- }
-
- fn main() {
- let path = "a.txt";
-
- let keyword_map = make_keyword_map(path);
- let (line_cnt, line_map) = make_line_map(path, &keyword_map);
-
- // 排序
- let mut keys = line_map.keys().collect::<Vec<_>>();
- keys.sort_unstable_by(|a, b| b.cmp(a));
-
- // 确定每行的顺序
- let mut line_order = vec![0; line_cnt + 1]; // TODO: 我没记错的话当 line_cnt 很大时会出问题
- let mut order = 0 as u32;
- for key in keys {
- let idxs = line_map.get(key).unwrap();
- for &idx in idxs {
- line_order[idx as usize] = order;
- order += 1;
- }
- }
-
- // 进行外排序
- let file = BufReader::new(File::open(path).expect("无法打开文件"));
- let mut sorter = ExternalSorter::new();
- sorter.set_max_size(1 * 1024 * 1024 * 1024 / 128); // 按每行 128 字节算, 1G 内存每次最多载入多少行
- let sorted_iter = sorter
- .sort_by(
- file.lines()
- .enumerate()
- .map(|(idx, s)| Line(idx as u32, s.unwrap())),
- |a, b| line_order[a.0 as usize].cmp(&line_order[b.0 as usize]),
- )
- .unwrap();
-
- // 输出
- let stdout = io::stdout();
- let mut handle = stdout.lock();
- for line in sorted_iter {
- handle.write(line.1.as_bytes()).unwrap();
- handle.write(&[b'\n']).unwrap();
- }
- }
复制代码
作者: bailong360 时间: 2019-3-20 11:23
想了想 100G 的话, 就算每行 80 个字符, 也有 10+ 亿行.
但 2G 内存就是连 10 亿个 int32 都放不下...
也就是说上面的代码还是应付不了数百GB的数据...这任务确实挺麻烦的= =
作者: 523066680 时间: 2019-3-20 12:53
本帖最后由 523066680 于 2019-3-20 17:06 编辑
回复 10# bailong360
我用随机数据50M做测试,9楼 Rust 内存消耗峰值700M,耗时约 1 分38秒(程序输出重定向到 >nul ) CPU 频率 4GHz- 12:52:01.14
- Finished dev [unoptimized + debuginfo] target(s) in 0.14s
- Running `target\debug\tmp.exe`
- 12:53:38.68
复制代码
用 tigerpower 工具生成的50M结果做测试,rust 出现错误提示:- thread 'main' panicked at 'attempt to subtract with overflow', src\main.rs:23:17
-
- note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
- error: process didn't exit successfully: `target\debug\tmp.exe` (exit code: 101)
复制代码
- for i in 0..bytes.len()-1 {
复制代码
应该是空行会导致此问题,rust 不熟,在外面加了层 if bytes.len() > 1 {}
耗时 2分钟07 秒。
我认为这个问题的主要消耗在两个阶段:
1. 统计阶段,字符串/数字串的划分其实是线性处理的,真正的消耗在于超大的哈希表,大量的键值对会导致每一次查询都很慢。
2. 排序阶段,考虑归并排序(分而治之)。
随机内容的文件样本(这个版本没有空行):
https://pan.baidu.com/s/1F9KCxQKgIbbaS5PlKDNoBg
提取码:att3
期待更多尝试
作者: tigerpower 时间: 2019-3-20 13:07
估计论坛里有不少人没有编译器,我上传一个编译好的,方便大家生成样本。
只要用的命令一样,大家在自己机器上能生成一样的样本,方便比较。
用法:gensample 字节数 [seed] [p] > 文件名.txt
seed为任意整数,缺省时为1
p为1时,打印空行,缺省时不打印空行
比如生成100M的样本:- gensample 104857600 > sample_100M.txt
复制代码
生成1000字节,seed为2019的样本:- gensample 1000 2019 > sample_1000_2019.txt
复制代码
生成3000字节保留空行的样本:- gensample 3000 1 1 > sample_3000_p.txt
复制代码
将100字节样本在屏幕上输出:复制代码
大家生成的都一样,可用MD5校验:- 49199e9274dd58d870227d12c1a20d3d *sample_100M.txt
- b7cb800dd095a113091d21c438cee872 *sample_1000_2019.txt
- b5192c110dad4f25ef1e4d0ad3d674a2 *sample_3000_p.txt
复制代码
100G的话是107374182400字节,请各位注意查看自己的硬盘剩余空间,谨慎使用
作者: bailong360 时间: 2019-3-20 17:59
本帖最后由 bailong360 于 2019-3-20 18:07 编辑
回复 11# 523066680
你这是 debug 模式, Rust 的 debug 模式巨慢(但编译很快). 要用 release 模式编译. 命令 cargo build --release
追求更卓越性能的话应该这样- :: 使用针对本机 CPU 优化过的代码
- set RUSTFLAGS="-C target_cpu=native"
- cargo build --release
复制代码
有数据生成器就好办了, 我看再来优化一下
作者: bailong360 时间: 2019-3-20 18:20
用 @tigerpower 提供的生成器生成了 100MB 随机数据
目前耗时 27s, 内存占用 819MB
profile 了一下确实有 1/3 的时间花在了 HashMap 相关操作上
作者: bailong360 时间: 2019-3-20 23:17
本帖最后由 bailong360 于 2019-3-21 00:07 编辑
又优化了一下, 代码放到 GitHub 上了: https://github.com/Aloxaf/Rust-toys/tree/master/biglog_sort
主要改动如下:
1. 更换了 HashMap 的实现
2. 使用了多线程排序
3. 更换了内存分配器
4. 记录字符串出现次数的时候不保存字符串本身, 而是保存其 Hash
目前 100MB 耗时 11s+, 内存占用 614MB
(耐着性子测了一下 1GB, 耗时 3:12, 内存占用 4.2GB......看来中间数据也不能放在内存里, 这任务真的挺麻烦的= =
PS. 更新了签名图片
=== 深夜更新 ===
试着学 LZ 在 CU 的帖子里的方法, 只记录出现次数最高的项
100MB 耗时瞬间降到了 7s, 内存占用 282MB
1GB 耗时降到了 2:00, 内存占用 1GB
原来那么大的内存都用来存每行的每个字符串的出现次数了......
作者: 523066680 时间: 2019-3-21 10:53
本帖最后由 523066680 于 2019-3-21 11:02 编辑
回复 16# bailong360
强大,感觉 Rust 前景很明朗(我也想学,不过看英文PDF很吃力就放弃了)。
https://github.com/Aloxaf/MirageTankGo 上班时间点开这个雷姆吓一跳,已 follow
我当时的处理方案是用 Perl 的 DB_File 模块,把哈希表绑定到使用磁盘文件存储的数据结构(内部实现是 BTree?),模块简化了问题(所以我现在也还没学BTREE),暂时避免了爆内存。
作者: bailong360 时间: 2019-3-21 11:42
回复 17# 523066680
当时随便找的图片 XD, 我有时间找张 SFW 的图片换上去
不要放弃啊, Rust 有官方文档的翻译: https://kaisery.github.io/trpl-zh-cn/
国内也出版了 Rust编程之道 和 深入浅出Rust, 资料其实也不算少了
我也试试找个嵌入式数据库, 看内存会下降多少(&耗时会上升多少
欢迎光临 批处理之家 (http://bathome.net./) |
Powered by Discuz! 7.2 |