【问题标题】:Simplest way to read a CSV file mapped to memory?读取映射到内存的 CSV 文件的最简单方法?
【发布时间】:2014-05-16 15:59:27
【问题描述】:

当我在 C++(11) 中读取文件时,我使用以下方法将它们映射到内存中:

boost::interprocess::file_mapping* fm = new file_mapping(path, boost::interprocess::read_only);
boost::interprocess::mapped_region* region = new mapped_region(*fm, boost::interprocess::read_only);
char* bytes = static_cast<char*>(region->get_address());

当我希望以极快的速度逐字节读取时,这很好。但是,我创建了一个 csv 文件,我想将其映射到内存,读取每一行并用逗号分割每一行。

有没有办法通过对上面的代码进行一些修改来做到这一点?

(我正在映射到内存,因为我有大量内存,并且我不希望磁盘/IO 流出现任何瓶颈)。

【问题讨论】:

    标签: c++ csv boost io memory-mapped-files


    【解决方案1】:

    这是我对“足够快”的看法。它在约 1 秒内压缩通过 116 MiB 的 CSV(2.5Mio 行[1])。

    然后可以在零副本中随机访问结果,因此没有开销(除非页面被换出)。

    比较:

    • 这比天真的 wc csv.txt 处理同一个文件要快 ~3 倍
    • 它与以下 perl 单行(列出所有行上的不同字段计数)一样快:

      perl -ne '$fields{scalar split /,/}++; END { map { print "$_\n" } keys %fields  }' csv.txt
      
    • 它只比 (LANG=C wc csv.txt) 慢,后者避免了语言环境功能(大约 1.5 倍)

    这是解析器的全部荣耀:

    using CsvField = boost::string_ref;
    using CsvLine  = std::vector<CsvField>;
    using CsvFile  = std::vector<CsvLine>;  // keep it simple :)
    
    struct CsvParser : qi::grammar<char const*, CsvFile()> {
        CsvParser() : CsvParser::base_type(lines)
        {
            using namespace qi;
    
            field = raw [*~char_(",\r\n")] 
                [ _val = construct<CsvField>(begin(_1), size(_1)) ]; // semantic action
            line  = field % ',';
            lines = line  % eol;
        }
        // declare: line, field, fields
    };
    

    唯一棘手的事情(也是唯一的优化)是从具有匹配字符数的源迭代器构造 CsvField 的语义操作。

    这里是主要的:

    int main()
    {
        boost::iostreams::mapped_file_source csv("csv.txt");
    
        CsvFile parsed;
        if (qi::parse(csv.data(), csv.data() + csv.size(), CsvParser(), parsed))
        {
            std::cout << (csv.size() >> 20) << " MiB parsed into " << parsed.size() << " lines of CSV field values\n";
        }
    }
    

    打印

    116 MiB parsed into 2578421 lines of CSV values
    

    你可以像std::string一样使用这些值:

    for (int i = 0; i < 10; ++i)
    {
        auto l     = rand() % parsed.size();
        auto& line = parsed[l];
        auto c     = rand() % line.size();
    
        std::cout << "Random field at L:" << l << "\t C:" << c << "\t" << line[c] << "\n";
    }
    

    打印例如:

    Random field at L:1979500    C:2    sateen's
    Random field at L:928192     C:1    sackcloth's
    Random field at L:1570275    C:4    accompanist's
    Random field at L:479916     C:2    apparel's
    Random field at L:767709     C:0    pinks
    Random field at L:1174430    C:4    axioms
    Random field at L:1209371    C:4    wants
    Random field at L:2183367    C:1    Klondikes
    Random field at L:2142220    C:1    Anthony
    Random field at L:1680066    C:2    pines
    

    完整的工作示例在这里 Live On Coliru


    [1]我通过重复附加

    的输出来创建文件
    while read a && read b && read c && read d && read e
    do echo "$a,$b,$c,$d,$e"
    done < /etc/dictionaries-common/words
    

    csv.txt,直到它计算出 250 万行。

    【讨论】:

    【解决方案2】:

    只需从您的内存映射字节创建一个 istringstream 并使用解析它:

    const std::string stringBuffer(bytes, region->get_size());
    std::istringstream is(stringBuffer);
    typedef boost::tokenizer< boost::escaped_list_separator<char> > Tokenizer;
    std::string line;
    std::vector<std::string> parsed;
    while(getline(is, line))
    {
        Tokenizer tokenizer(line);
        parsed.assign(tokenizer.begin(),tokenizer.end());
        for (auto &column: parsed)
        {
            // 
        }
    }
    

    请注意,与顺序读取相比,在许多系统上,内存映射并没有提供任何速度优势。在这两种情况下,您最终都会逐页读取磁盘中的数据,可能具有相同的预读量,并且两种情况下的 IO 延迟和带宽都是相同的。无论您是否有大量内存都不会产生任何影响。此外,根据系统的不同,memory_mapping,即使是只读的,也可能会导致令人惊讶的行为(例如保留交换空间),这有时不会让人们忙于故障排除。

    【讨论】:

    • 所以内存映射不会预先加载文件?我以为是的!
    • 将其加载到字符串流中确保您首先复制所有内存
    • @user997112 不,它不会将文件预先加载到物理内存中,这是一件好事。首先是非常大的文件会出现问题,然后它会阻止您覆盖 IO 和处理。即使将其加载到字符串流中会像 sehe 所说的那样将每个页面加载到内存中,但它不能保证此时整个文件都在 RAM 中(内存页面可能会被丢弃)。
    • 我已经使用 Boost 制作了一个示例来解析整个源代码,而不是满足于缓慢和使用 std::stringstd::stringstream 进行复制。它可以做得更快,但这肯定是一个好的开始。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-06
    • 1970-01-01
    相关资源
    最近更新 更多