批处理之家's Archiver

523066680 发表于 2019-1-29 19:07

[挑战]100G文本统计+排序

[i=s] 本帖最后由 523066680 于 2019-1-30 11:16 编辑 [/i]

源自ChinaUnix一个用户提出的问题,当时帮题主理清基础的处理方式,但是上G的文件依然没有写出高效率的代码。

原帖1,先从较简短的示例描述
[url]http://bbs.chinaunix.net/thread-4263348-1-1.html[/url]
[quote]
[size=2][color=#999999][url=http://bbs.chinaunix.net/thread-4263348-1-1.html]Windows19 发表于 2017-06-20 10:54[/url][/color]
有几百GB,LOG,乱出八粗的

找出在文本内重复字符串从多到少打印,

找出重复次数最多字符串,然后整行打印  (注: 按2种类型条件判断统计,[b]重复字母串次数[/b],[b]重复数字串次数[/b],然后由多到少整行打印出来). 剩余的行也需一并打印出来(出来结果应和原LOG行数一致)

找出两种类型字符串在文本中重复最多次数,从多到少排序

a.txt[code]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
[/code]按条件统计后应该得到(人工审核不知道有没错)[code]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[/code][/quote]

这个题主是个典型的令人头痛的类型,因为他不把问题描述完整,一开始还发过几个简化过的问题的帖子,等到有人回复,他又渐渐把完整的问题浮出水面(可能有些细节他自己都不确定)
我在另一个帖子对处理方案做了详细说明,题主认为符合需求,可供参考:
[quote]
[url=http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=4263410&page=7#pid24637988]523066680 发表于 2017-06-22 23:12[/url]
以原贴的段落为例,
[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
[/quote]

注意上面的结果是同一“单词”在单行中重复多次记为一次的。应考虑按多次计算的情况(或者做个开关允许按不同方式处理)
[quote]65425855662efssaezsdfcsf/        /sff.sdf/'[color=#ff0000]s[/color];f]  \sDed33[color=#ff0000]s[/color]00000000
一行内两次算是2次吗?我以为一行内多次也计为1次,还特地做了判断

[url=http://bbs.chinaunix.net/forum.php?mod=redirect&goto=findpost&ptid=4263410&pid=24637894&fromuid=26822391]Windows19 回复 53# 523066680[/url]
如果算2次影响效率
那在一行内出现相同的算1次也可以
[/quote]
当然这个细节不是主要问题,数据量大以后要考虑哈希表的内存占用,如果改用文件存储数据结构,则需要考虑效率、硬盘占用问题。
"分而治之"策略 和 归并排序 应该是少不了的,个人认为是很好的锻炼。

当时(17年)的代码虽然借用DB_File模块支持了大文件处理,但效率极低,正打算重写。
为了减少过度的测试时间和硬盘消耗,将此问题稍作修改,改为10G文件(或1G),语言不限,大家有兴趣吗?

523066680 发表于 2019-1-29 19:26

补充 RE: [挑战]100G文本统计+排序

[i=s] 本帖最后由 523066680 于 2019-3-20 19:52 编辑 [/i]

这个问题有一个分支剧情,就是当时我建议题主去C++板块提问,windoze版主给他提出了解答此题6000元的报价
[quote][url=http://bbs.chinaunix.net/forum.php?mod=redirect&goto=findpost&ptid=4263518&pid=24638179&fromuid=26822391]windoze 回复 3# Windows19[/url]

nonono,问题不是支付方式,而是金额。
你要的这个东西显然已经超出论坛技术讨论的范围,我拍脑袋的估一下差不多要一周左右的工作量,假设你找了一个月薪20000的人帮你做这件事,至少就得掏5000块,而且一般零活的价钱会在月结工资的基础上再向上浮动10~20%。

不知道lz有没有做好掏6000块的心理准备。[/quote]
想借windoze版主的话提醒各位一下,技术是有价值的,像lxh发的那些各种需求,十几二十元,三五人去给他轮番全方位解答,着实是在贬低技术的价值。
以及windoze为这个问题评估了一个价值,各位如果感兴趣可以自己试试,有话题,可以讨论,代码也未必要发出来(几千大洋 [img]http://www.bathome.net/images/smilies/default/lol.gif[/img])。

[b][color=DarkRed]-----------------  2019-03-19 补充 -----------------[/color][/b]
生成随机文本的代码(初步),
文件大小通过 FILE_SIZE 指定,效率偏低,在我这里(CPU 4GHz) 生成100M需要 13秒,太慢。欢迎补充更好的生成工具。
g++ -std=c++11 -O3 -o gen gen.cpp

[b][color=DarkRed]-----------------  2019-03-20 更新 -----------------[/color][/b]
g++ -std=c++11 -O3 -o gen gen.cpp
本机测试 100M 1.3秒[code]// 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();
}
[/code][b][color=DarkRed]-----------------  2019-03-21 更新 -----------------[/color][/b]
12楼提供了可执行的生成工具,我这里附一个小批处理,方便修改(但注意变量值范围限制)。[code]@echo off
set /a MB=1^<^<20, SIZE=MB*100, SEED=2019
gensample %SIZE% %SEED%>F:/CU_Topics/a.txt
pause
[/code]

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 简单试了下[code]
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();
        }
    }
}
[/code]没有测试文本, 就拿1L的文本复制粘贴了100MB测试了一下.
时间 7s
内存占用 176MB

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

523066680 发表于 2019-3-19 18:38

[i=s] 本帖最后由 523066680 于 2019-3-19 18:53 编辑 [/i]

[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=218404&ptid=51964]6#[/url] [i]bailong360[/i] [/b]

      在 bathome 看到 Rust 代码,很新鲜。
最近在知乎偶然看到 CU 的那位版主(徐晨),应该 Rust 他也熟的

[url]https://zhuanlan.zhihu.com/p/38948830[/url]
[img]http://img.gzsophy.com/bathome/windoz.png[/img]


我自己的重制版还没写出来,惭愧 [img]http://www.bathome.net/images/smilies/grapeman/23.gif[/img]

老刘1号 发表于 2019-3-19 19:12

mark一下,,

bailong360 发表于 2019-3-19 23:29

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

[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=218405&ptid=51964]7#[/url] [i]523066680[/i] [/b]


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


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

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

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

主体代码如下, 完整代码传百度云了(论坛上传竟然要 Flash 支持?...
链接: [url=https://pan.baidu.com/s/1lvI4UPwOclizsfp108j0EQ]https://pan.baidu.com/s/1lvI4UPwOclizsfp108j0EQ[/url] 提取码: s39j[code]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();
    }
}
[/code]

bailong360 发表于 2019-3-20 11:23

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

523066680 发表于 2019-3-20 12:53

[i=s] 本帖最后由 523066680 于 2019-3-20 17:06 编辑 [/i]

[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=218432&ptid=51964]10#[/url] [i]bailong360[/i] [/b]

    我用随机数据50M做测试,9楼 Rust 内存消耗峰值700M,耗时约 1 分38秒(程序输出重定向到 >nul ) CPU 频率 4GHz[code]12:52:01.14
    Finished dev [unoptimized + debuginfo] target(s) in 0.14s
     Running `target\debug\tmp.exe`
12:53:38.68[/code]用 tigerpower 工具生成的50M结果做测试,rust 出现错误提示:[code]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)[/code][code]for i in 0..bytes.len()-1 {[/code]应该是空行会导致此问题,rust 不熟,在外面加了层 if bytes.len() > 1 {}
耗时 2分钟07 秒。

我认为这个问题的主要消耗在两个阶段:
1. 统计阶段,字符串/数字串的划分其实是线性处理的,真正的消耗在于超大的哈希表,大量的键值对会导致每一次查询都很慢。
2. 排序阶段,考虑归并排序(分而治之)。

随机内容的文件样本(这个版本没有空行):
[url]https://pan.baidu.com/s/1F9KCxQKgIbbaS5PlKDNoBg[/url]
提取码:att3

期待更多尝试

tigerpower 发表于 2019-3-20 13:07

估计论坛里有不少人没有编译器,我上传一个编译好的,方便大家生成样本。
只要用的命令一样,大家在自己机器上能生成一样的样本,方便比较。
用法:gensample 字节数 [seed] [p] > 文件名.txt
seed为任意整数,缺省时为1
p为1时,打印空行,缺省时不打印空行
比如生成100M的样本:[code]gensample 104857600 > sample_100M.txt[/code]生成1000字节,seed为2019的样本:[code]gensample 1000 2019 > sample_1000_2019.txt[/code]生成3000字节保留空行的样本:[code]gensample 3000 1 1 > sample_3000_p.txt[/code]将100字节样本在屏幕上输出:[code]gensample 100 2>NUL[/code]大家生成的都一样,可用MD5校验:[code]49199e9274dd58d870227d12c1a20d3d *sample_100M.txt
b7cb800dd095a113091d21c438cee872 *sample_1000_2019.txt
b5192c110dad4f25ef1e4d0ad3d674a2 *sample_3000_p.txt
[/code]100G的话是107374182400字节,请各位注意查看自己的硬盘剩余空间,谨慎使用:D

bailong360 发表于 2019-3-20 17:59

[i=s] 本帖最后由 bailong360 于 2019-3-20 18:07 编辑 [/i]

[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=218438&ptid=51964]11#[/url] [i]523066680[/i] [/b]


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

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

bailong360 发表于 2019-3-20 18:20

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

bailong360 发表于 2019-3-20 23:17

[i=s] 本帖最后由 bailong360 于 2019-3-21 00:07 编辑 [/i]

又优化了一下, 代码放到 GitHub 上了: [url]https://github.com/Aloxaf/Rust-toys/tree/master/biglog_sort[/url]

主要改动如下:
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

[i=s] 本帖最后由 523066680 于 2019-3-21 11:02 编辑 [/i]

[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=218477&ptid=51964]16#[/url] [i]bailong360[/i] [/b]

    强大,感觉 Rust 前景很明朗(我也想学,不过看英文PDF很吃力就放弃了)。
[url]https://github.com/Aloxaf/MirageTankGo[/url] 上班时间点开这个雷姆吓一跳,已 follow

我当时的处理方案是用 Perl 的 DB_File 模块,把哈希表绑定到使用磁盘文件存储的数据结构(内部实现是 BTree?),模块简化了问题(所以我现在也还没学BTREE),暂时避免了爆内存。

bailong360 发表于 2019-3-21 11:42

[b]回复 [url=http://www.bathome.net/redirect.php?goto=findpost&pid=218490&ptid=51964]17#[/url] [i]523066680[/i] [/b]

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

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

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

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

页: [1]

Powered by Discuz! Archiver 7.2  © 2001-2009 Comsenz Inc.