C++的 set 爆内存,求助

2017-10-25 09:42:17 +08:00
 scenix

如题,本来想写个小程序,对比一下 c++,python,golang 处理日志文件的效率。找了个 600MB 的日志文件试水,结果因为本人水平有限,导致 c++的实现出现了大量的内存消耗,运行不完就被 kill 了。

程序的思路是逐行读取日志文件,用空格切分,第一个字段作为 key,剩下的字段去重后作为 value。

先贴一下 python 的实现

big_dict = {}
with open("data/test_log.txt") as f_in:
    for line in f_in:
        items = line.strip().split(' ')
        key = items[0]
        if key not in big_dict:
            big_dict[key] = set([])
        for i in items[1:]:
            big_dict[key].add(i)

print "total keys:", len(big_dict)

再贴一下 golang 的:

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strings"
)


func process(fname string, big_dict map[string]map[string]int) {
    fin, err := os.Open(fname)
    defer fin.Close()
    if err != nil {
        fmt.Println("Open file error ", err)
        return
    }

    buf := bufio.NewReader(fin)
    line_count := 0
    for ; ; line_count++ {
        line, err := buf.ReadString('\n')
        if err != nil {
            if io.EOF == err {
                break
            }
            fmt.Println("Error in ReadBytes: ", err)
            return
        }
        items := strings.Split(line, " ")
        key := items[0]

        elem, ok := big_dict[key]
        if false == ok {
            big_dict[key] = make(map[string]int)
        }
        elem = big_dict[key]
        for i := 1; i < len(items); i++ {
            elem[items[i]] = 1
        }
    }
    fmt.Println("Total Line Count: ", line_count)
}

func main() {
    const fname = "data/test_log.txt"
    big_dict := make(map[string]map[string]int)
    process(fname, big_dict)
    fmt.Println("Total Key Count: ", len(big_dict))
}

最后贴一下 c++的。

#include <iostream>
#include <fstream>
#include <string>
#include<unordered_map>
#include<unordered_set>

using namespace std;

// data/test_log.txt 文件是一个 600MB 的日志文件
const string IN_FNAME = "data/test_log.txt";
unordered_map<string, unordered_set<string *>* > big_dict;

void process_file(const string fname, unordered_map<string,unordered_set<string*> *> & big_dict) {
    ifstream f_in;
    f_in.open(fname, ios::in);
    string line = "";
    int total_line = 0;
    size_t start =0, end = 0;
    while(getline(f_in, line)) {
        ++total_line;
        start =0, end = 0;// c++没有内建 string 的分割,自己 diy 个
        end = line.find_first_of(' ',start);
        string key = line.substr(start,end);
        // 寻找是否存在 key
        if(big_dict.find(key) == big_dict.end()) {
            unordered_set<string*> *p = new unordered_set<string*>;
            big_dict[key] = p;
        }

        start = end+1;
        while(start<line.size()) {
            end = line.find_first_of(' ',start);
            if(string::npos == end) end = line.size() - 1;
            string value = line.substr(start,end);
            big_dict[key]->insert(&value);//大部分的时间都在这个 insert 上了
            //这里我存的是 string 的指针,实际上无法得到去重的效果
            //但是我如果直接存 string 对象,会直接爆内存
            start = end + 1;
        }
    }
    f_in.close();

}

int main() {

    process_file(IN_FNAME, big_dict);

    cout<<"Total Keys:"<<big_dict.size()<<endl;

    return 0;
}

c++的实现中,big_dict[key]->insert(&value);大部分的时间都在这个 insert 上了,这里我存的是 string 的指针,实际上无法得到去重的效果。但是我如果直接存 string 对象,会直接爆内存。我存指针可以解决内存的问题,但是执行速度上依然是 go 快过 python,最慢的是 c++。

希望有大神能指点下,看我的 c++代码哪里出了问题。

4387 次点击
所在节点    C
48 条回复
wevsty
2017-10-26 12:49:46 +08:00
@scenix 跑了一下 O2 优化,0.18 秒。
然而写成这样子,真的很难看,基本丧失了复用性。
xziar
2017-10-26 13:27:32 +08:00
@scenix 不明白你为什么还是要用 set 的指针做 value。
有 new 没 delete,这就不是好的 C++代码,跟别提代码耦合在一起了。

我随便找了自己的文档测了一下,程序热点在 hash,其次是 getline。
vs2017.4 x64 默认 o2
scenix
2017-10-26 14:13:47 +08:00
scenix
2017-10-26 14:17:12 +08:00
@xziar
@wevsty 感谢二位,我这边只是随手写的测试代码,想看下不同语言在面对类似情景下的性能表现,没打算复用,东西存进去就没打算放出来,也就没写 delete。
不过我修改了一下 python 的代码,性能也有不少提高,二位能否看下这段 python 在你们机器上的表现?
我本机如果用 pypy 去跑,性能和 c++几乎一样,真是意外。

big_dict = {}
with open("data/test_log.txt") as f_in:
for line in f_in:
items = line.strip().split(' ')
key = items[0]
if key not in big_dict:
big_dict[key] = set(items[1:])
else:
big_dict[key].update(items[1:]) // 原来的 for 循环去掉了,直接用 set 内建的 update 方法
print "total keys:", len(big_dict)
yokuhama
2017-10-26 16:20:47 +08:00
粗看下来,楼主的那个 map 申请的是栈内存,而非堆内存,栈是有大小限制的。。。。然而效率问题只能实测,不能说谁快谁慢。
ryd994
2017-10-26 20:45:48 +08:00
与其让网友帮你隔空 debug,为什么不把自己的数据 print 出来呢?
printf debug 不丢人,不是什么事情都要找 gdb 的
valgrind is your friend. 我个人的习惯是提交前一定要 valgrind 查一下
xziar
2017-10-28 02:21:19 +08:00
@yokuhama 无论什么都会用到栈内存,但 unordered_map 的数据肯定是在堆上的。

@scenix 一段代码的效率和测试数据也有关的,可能我的数据倾向于往 set 里塞重复东西,你的数据却重复比较少。不同数据没什么好比的。
C++的话,自带的 STL 的 unordered 并不见得就是效率最高的,换用其他第三方库的 hashmap 可能效率能更高。能预估数据大小的话也可以提前预留些空间。
至于 python 和 C++速度相等也不是没可能啊。
scenix
2017-10-28 17:03:20 +08:00
@xziar 就是心里不平衡,老子费劲巴拉的写了一两天,还让各路神仙隔空 debug,结果跟 10 行 python 差不多。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/400452

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX