簡體   English   中英

讀取映射到內存的CSV文件的最簡單方法是什么?

[英]Simplest way to read a CSV file mapped to memory?

當我從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流的瓶頸)。

這是我對“足夠快”的看法。 它在~1秒內拉過116 MiB的CSV(2.5Mio線[1] )。

然后可以在零拷貝時隨機訪問結果,因此沒有開銷(除非頁面被換出)。

為了比較:

  • 這比天真的wc csv.txt 快3倍於同一個文件
  • 它與下面的perl one liner一樣快(它列出了所有行上的不同字段計數):

     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萬行。

只需從內存映射字節創建一個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甚至是只讀,可能會導致令人驚訝的行為(例如,保留交換空間),而這些行為有時會讓人們忙於排除故障。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM