[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖
嘛, 几千大洋是在 100G 数据, 2G 内存, C++ 编写 这几个条件下算出的价格
数据量减小一百倍(OR 内存增大一百倍[如果有钱的话]), 同时换简单点的语言的话, 价格应该就没那么高了
那位老哥显然没有理解"量变引起质变"这句话

用 Rust 简单试了下
  1. use lazy_static::lazy_static;
  2. use regex::Regex;
  3. use std::collections::HashMap;
  4. use std::fs::File;
  5. use std::io::{self, BufRead, BufReader, Write};
  6. /// 从字符串中提取出"字符串"
  7. fn get_keywords(text: &str) -> Vec<&str> {
  8.     lazy_static! {
  9.         static ref RE: Regex = Regex::new(r#"\d+|[a-zA-Z]+"#).unwrap();
  10.     }
  11.     RE.find_iter(text)
  12.         .map(|m| &text[m.start()..m.end()])
  13.         .collect()
  14. }
  15. fn main() {
  16.     let path = "a.txt";
  17.     let file = File::open(path).expect("无法打开文件");
  18.     let file = BufReader::new(file);
  19.     // 遍历全文, 确定每个"字符串"出现次数
  20.     let mut keyword_map = HashMap::new();
  21.     for line in file.lines() {
  22.         let line = line.unwrap();
  23.         for keyword in get_keywords(&line) {
  24.             // TODO: 此处的 clone 可避免否?
  25.             *keyword_map.entry(keyword.to_owned()).or_insert(0) += 1;
  26.         }
  27.     }
  28.     // 再次遍历, 确定每行所包含的字符串的出现次数
  29.     let file = BufReader::new(File::open(path).unwrap());
  30.     let mut line_map = HashMap::new();
  31.     for line in file.lines() {
  32.         let line = line.unwrap();
  33.         let keywords = get_keywords(&line);
  34.         let mut occurs_cnt = keywords.iter().map(|&s| *keyword_map.get(s).unwrap()).collect::<Vec<_>>();
  35.         occurs_cnt.sort_unstable_by(|a, b| b.cmp(a));
  36.         line_map.entry(occurs_cnt).or_insert(vec![]).push(line);
  37.     }
  38.     // 排序
  39.     let mut keys = line_map.keys().collect::<Vec<_>>();
  40.     keys.sort_unstable_by(|a, b| b.cmp(a));
  41.     // 输出
  42.     let stdout = io::stdout();
  43.     let mut handle = stdout.lock();
  44.     for key in keys {
  45.         let lines = line_map.get(key).unwrap();
  46.         for line in lines {
  47.             handle.write(line.as_bytes()).unwrap();
  48.             handle.write(&[b'\n']).unwrap();
  49.         }
  50.     }
  51. }
复制代码
没有测试文本, 就拿1L的文本复制粘贴了100MB测试了一下.
时间 7s
内存占用 176MB

内存占用大了点, 毕竟 Rust 好像没啥成熟的外排序库, 也不想手写 (
同时 profile 了一下发现 1/3 多的时间竟然花在了正则上面...这个倒是可以考虑手写一下
1

评分人数

TOP

最近在学 Rust, 感觉非常好用~

回复 7# 523066680


    诶, 这位我有印象. 没想到竟然是 CU 的版主


我最终还是找了个 API 很难用的 crate 改了下, 先用起来再说

这次 100MB 用时 4.4s, 最高内存占用 200MB
看起来内存占用并没有减少, 嘛...因为文件大小太小了看不出来, 我也没耐心弄个几G的文件测试

加上多线程应该还能快一点, 不过试了下加上效果也不是很明显, 估计瓶颈应该在 IO 上 (也有可能是文件太小了...

主体代码如下, 完整代码传百度云了(论坛上传竟然要 Flash 支持?...
链接: https://pan.baidu.com/s/1lvI4UPwOclizsfp108j0EQ 提取码: s39j
  1. use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
  2. use extsort::*;
  3. use std::collections::HashMap;
  4. use std::fs::File;
  5. use std::io::{self, BufRead, BufReader, Read, Write};
  6. /// 从字符串中提取出"字符串"
  7. fn get_keywords(text: &str) -> Vec<&str> {
  8.     #[inline]
  9.     fn type_of(b: u8) -> u8 {
  10.         if b.is_ascii_alphabetic() {
  11.             return 1;
  12.         } else if b.is_ascii_digit() {
  13.             return 2;
  14.         } else {
  15.             return 3;
  16.         }
  17.     }
  18.     let bytes = text.as_bytes();
  19.     let mut ret = vec![];
  20.     let mut pos = 0;
  21.     for i in 0..bytes.len() - 1 {
  22.         if bytes[i].is_ascii_alphanumeric() && type_of(bytes[i]) != type_of(bytes[i + 1]) {
  23.             ret.push(&text[pos..=i]);
  24.             pos = i + 1;
  25.         } else if !bytes[i].is_ascii_alphanumeric() {
  26.             pos = i + 1;
  27.         }
  28.     }
  29.     if bytes[pos].is_ascii_alphanumeric() && type_of(bytes[pos]) == type_of(bytes[bytes.len() - 1])
  30.     {
  31.         ret.push(&text[pos..]);
  32.     }
  33.     ret
  34. }
  35. /// 读取文件, 确定每个"字符串"出现的次数
  36. fn make_keyword_map(path: &str) -> HashMap<String, u32> {
  37.     let file = BufReader::new(File::open(path).expect("无法打开文件"));
  38.     let mut ret = HashMap::new();
  39.     for line in file.lines() {
  40.         let line = line.unwrap();
  41.         for keyword in get_keywords(&line) {
  42.             *ret.entry(keyword.to_owned()).or_insert(0) += 1;
  43.         }
  44.     }
  45.     ret
  46. }
  47. /// 根据每行的字符串出现次数生成一个"特征值"
  48. fn make_line_map(
  49.     path: &str,
  50.     keyword_map: &HashMap<String, u32>,
  51. ) -> (usize, HashMap<Vec<u32>, Vec<u32>>) {
  52.     let file = BufReader::new(File::open(path).expect("无法打开文件"));
  53.     let mut line_cnt = 0;
  54.     let mut ret = HashMap::new();
  55.     for (idx, line) in file.lines().enumerate() {
  56.         let line = line.unwrap();
  57.         let keywords = get_keywords(&line);
  58.         let mut occurs_cnt = keywords
  59.             .iter()
  60.             .map(|&s| *keyword_map.get(s).unwrap())
  61.             .collect::<Vec<_>>();
  62.         occurs_cnt.sort_unstable_by(|a, b| b.cmp(a));
  63.         ret.entry(occurs_cnt).or_insert(vec![]).push(idx as u32);
  64.         line_cnt = idx;
  65.     }
  66.     (line_cnt, ret)
  67. }
  68. #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
  69. struct Line(u32, String);
  70. impl Sortable<Line> for Line {
  71.     fn encode(item: Line, write: &mut Write) {
  72.         write.write_u32::<LittleEndian>(item.0).unwrap();
  73.         write.write(item.1.as_bytes()).unwrap();
  74.         write.write(&[b'\n']).unwrap();
  75.     }
  76.     fn decode(read: &mut Read) -> Option<Line> {
  77.         let idx = read.read_u32::<LittleEndian>().ok()?;
  78.         let mut bytes = read.bytes();
  79.         let s = String::from_utf8(
  80.             bytes
  81.                 .by_ref()
  82.                 .map(|b| b.unwrap())
  83.                 .take_while(|b| *b != b'\n')
  84.                 .collect(),
  85.         )
  86.         .unwrap();
  87.         // eprintln!("{} {}", idx, s);
  88.         Some(Line(idx, s))
  89.     }
  90. }
  91. fn main() {
  92.     let path = "a.txt";
  93.     let keyword_map = make_keyword_map(path);
  94.     let (line_cnt, line_map) = make_line_map(path, &keyword_map);
  95.     // 排序
  96.     let mut keys = line_map.keys().collect::<Vec<_>>();
  97.     keys.sort_unstable_by(|a, b| b.cmp(a));
  98.     // 确定每行的顺序
  99.     let mut line_order = vec![0; line_cnt + 1]; // TODO: 我没记错的话当 line_cnt 很大时会出问题
  100.     let mut order = 0 as u32;
  101.     for key in keys {
  102.         let idxs = line_map.get(key).unwrap();
  103.         for &idx in idxs {
  104.             line_order[idx as usize] = order;
  105.             order += 1;
  106.         }
  107.     }
  108.     // 进行外排序
  109.     let file = BufReader::new(File::open(path).expect("无法打开文件"));
  110.     let mut sorter = ExternalSorter::new();
  111.     sorter.set_max_size(1 * 1024 * 1024 * 1024 / 128); // 按每行 128 字节算, 1G 内存每次最多载入多少行
  112.     let sorted_iter = sorter
  113.         .sort_by(
  114.             file.lines()
  115.                 .enumerate()
  116.                 .map(|(idx, s)| Line(idx as u32, s.unwrap())),
  117.             |a, b| line_order[a.0 as usize].cmp(&line_order[b.0 as usize]),
  118.         )
  119.         .unwrap();
  120.     // 输出
  121.     let stdout = io::stdout();
  122.     let mut handle = stdout.lock();
  123.     for line in sorted_iter {
  124.         handle.write(line.1.as_bytes()).unwrap();
  125.         handle.write(&[b'\n']).unwrap();
  126.     }
  127. }
复制代码
1

评分人数

    • 523066680: Rust实战,我也顺带学一学技术 + 1

TOP

想了想 100G 的话, 就算每行 80 个字符, 也有 10+ 亿行.
但 2G 内存就是连 10 亿个 int32 都放不下...
也就是说上面的代码还是应付不了数百GB的数据...这任务确实挺麻烦的= =

TOP

本帖最后由 bailong360 于 2019-3-20 18:07 编辑

回复 11# 523066680


    你这是 debug 模式, Rust 的 debug 模式巨慢(但编译很快). 要用 release 模式编译. 命令 cargo build --release

追求更卓越性能的话应该这样
  1. :: 使用针对本机 CPU 优化过的代码
  2. set RUSTFLAGS="-C target_cpu=native"
  3. cargo build --release
复制代码
有数据生成器就好办了, 我看再来优化一下
1

评分人数

    • 523066680: 学习了。PS:你的签名图片失效了技术 + 1

TOP

用 @tigerpower 提供的生成器生成了 100MB 随机数据
目前耗时 27s, 内存占用 819MB
profile 了一下确实有 1/3 的时间花在了 HashMap 相关操作上

TOP

本帖最后由 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

原来那么大的内存都用来存每行的每个字符串的出现次数了......
1

评分人数

TOP

回复 17# 523066680

当时随便找的图片 XD, 我有时间找张 SFW 的图片换上去

不要放弃啊, Rust 有官方文档的翻译: https://kaisery.github.io/trpl-zh-cn/

国内也出版了 Rust编程之道 和 深入浅出Rust, 资料其实也不算少了

我也试试找个嵌入式数据库, 看内存会下降多少(&耗时会上升多少
1

评分人数

TOP

返回列表