我需要计算104gb文件中字符串"<page>"
的出现次数,以获取给定Wikipedia转储中的文章数量。首先,我试过这个。
grep -F '<page>' enwiki-20141208-pages-meta-current.xml | uniq -c
但是,grep会在一段时间后崩溃。因此,我写了以下程序。但是,它仅在我的机器上处理20mb / s的输入文件,这是我的HDD的大约5%的工作负载。我怎样才能加快这段代码的速度?
#include <iostream>
#include <fstream>
#include <string>
int main()
{
// Open up file
std::ifstream in("enwiki-20141208-pages-meta-current.xml");
if (!in.is_open()) {
std::cout << "Could not open file." << std::endl;
return 0;
}
// Statistics counters
size_t chars = 0, pages = 0;
// Token to look for
const std::string token = "<page>";
size_t token_length = token.length();
// Read one char at a time
size_t matching = 0;
while (in.good()) {
// Read one char at a time
char current;
in.read(¤t, 1);
if (in.eof())
break;
chars++;
// Continue matching the token
if (current == token[matching]) {
matching++;
// Reached full token
if (matching == token_length) {
pages++;
matching = 0;
// Print progress
if (pages % 1000 == 0) {
std::cout << pages << " pages, ";
std::cout << (chars / 1024 / 1024) << " mb" << std::endl;
}
}
}
// Start over again
else {
matching = 0;
}
}
// Print result
std::cout << "Overall pages: " << pages << std::endl;
// Cleanup
in.close();
return 0;
}
答案 0 :(得分:1)
假设文件中没有使用类似
的大行for (std::string line; std::getline(in, line); } {
// find the number of "<page>" strings in line
}
必然会更快 !将每个字符作为一个字符的字符串读取是您可能做的最糟糕的事情。变得越来越慢很难。对于每个角色,流将执行以下操作:
tie()
ed流(没有,即没有意义)。xsgetn()
。那里有很多浪费!
我无法想象为什么grep
会失败,除非某条线大量超过预期的最大线长。尽管使用std::getline()
和std::string()
可能会有更大的上限,但处理大线仍然无效。如果文件可能包含大量的行,那么使用以下内容可能更合理:
for (std::istreambuf_iterator<char> it(in), end;
(it = std::find(it, end, '<') != end; ) {
// match "<page>" at the start of of the sequence [it, end)
}
对于仍然做得太多的流的糟糕实现。好的实现将非常有效地调用std::find(...)
,并且可能会在一个处检查多个字符,仅为每个第16次循环迭代添加一个检查和循环。我希望上面的代码将您的CPU绑定实现转变为I / O绑定实现。糟糕的实现可能仍然受CPU限制,但它应该仍然要好得多。
无论如何,请记住启用优化!
答案 1 :(得分:1)
使用您的代码需要大约2.4秒而不是11.5秒。由于不计算换行符,总字符数略有不同,但我认为这是可以接受的,因为它仅用于显示进度。
void parseByLine()
{
// Open up file
std::ifstream in("enwiki-latest-pages-meta-current1.xml-p000000010p000010000");
if(!in)
{
std::cout << "Could not open file." << std::endl;
return;
}
size_t chars = 0;
size_t pages = 0;
const std::string token = "<page>";
std::string line;
while(std::getline(in, line))
{
chars += line.size();
size_t pos = 0;
for(;;)
{
pos = line.find(token, pos);
if(pos == std::string::npos)
{
break;
}
pos += token.size();
if(++pages % 1000 == 0)
{
std::cout << pages << " pages, ";
std::cout << (chars / 1024 / 1024) << " mb" << std::endl;
}
}
}
// Print result
std::cout << "Overall pages: " << pages << std::endl;
}
这是一个将每行添加到缓冲区然后在达到阈值时处理缓冲区的示例。与第一个版本相比,它需要2秒而不是2.4。我使用了几个不同的缓冲区大小的阈值,并且在固定数量(16,32,64,4096)的线之后进行处理,只要有一些批处理,它们似乎大致相同。感谢Dietmar的想法。
int processBuffer(const std::string& buffer)
{
static const std::string token = "<page>";
int pages = 0;
size_t pos = 0;
for(;;)
{
pos = buffer.find(token, pos);
if(pos == std::string::npos)
{
break;
}
pos += token.size();
++pages;
}
return pages;
}
void parseByMB()
{
// Open up file
std::ifstream in("enwiki-latest-pages-meta-current1.xml-p000000010p000010000");
if(!in)
{
std::cout << "Could not open file." << std::endl;
return;
}
const size_t BUFFER_THRESHOLD = 16 * 1024 * 1024;
std::string buffer;
buffer.reserve(BUFFER_THRESHOLD);
size_t pages = 0;
size_t chars = 0;
size_t progressCount = 0;
std::string line;
while(std::getline(in, line))
{
buffer += line;
if(buffer.size() > BUFFER_THRESHOLD)
{
pages += processBuffer(buffer);
chars += buffer.size();
buffer.clear();
}
if((pages / 1000) > progressCount)
{
++progressCount;
std::cout << pages << " pages, ";
std::cout << (chars / 1024 / 1024) << " mb" << std::endl;
}
}
if(!buffer.empty())
{
pages += processBuffer(buffer);
chars += buffer.size();
std::cout << pages << " pages, ";
std::cout << (chars / 1024 / 1024) << " mb" << std::endl;
}
}