[英]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.