C++ 如何加快计算大文件中单词的出现次数?

C++ 如何加快计算大文件中单词的出现次数?,c++,performance,optimization,file-io,C++,Performance,Optimization,File Io,我需要计算104gb文件中字符串“的出现次数,以获取给定Wikipedia转储中的文章数。首先,我试过这个 grep -F '<page>' enwiki-20141208-pages-meta-current.xml | uniq -c grep-F''enwiki-20141208-pages-meta-current.xml | uniq-c 然而,grep在一段时间后崩溃了。因此,我编写了以下程序。然而,它在我的机器上只处理20mb/s的输入文件,这大约是我硬盘的5%的工

我需要计算104gb文件中字符串
的出现次数,以获取给定Wikipedia转储中的文章数。首先,我试过这个

grep -F '<page>' enwiki-20141208-pages-meta-current.xml | uniq -c
grep-F''enwiki-20141208-pages-meta-current.xml | uniq-c
然而,grep在一段时间后崩溃了。因此,我编写了以下程序。然而,它在我的机器上只处理20mb/s的输入文件,这大约是我硬盘的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(&current, 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;
}
#包括
#包括
#包括
int main()
{
//打开文件
std::ifstream in(“enwiki-20141208-pages-meta-current.xml”);
如果(!in.is_open()){

std::cout假设文件中没有使用

for (std::string line; std::getline(in, line); } {
    // find the number of "<page>" strings in line
}
对于仍然做得太多的流的错误实现。好的实现将调用
std::find(…)
非常有效,并且可能一次检查多个字符,仅为每16次循环迭代添加一个检查和循环。我希望上面的代码将CPU绑定的实现转变为I/O绑定的实现。糟糕的实现可能仍然是CPU绑定的,但应该会更好


在任何情况下,请记住启用优化!

我使用此文件测试:

使用代码大约需要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;
}
void parseByLine()
{
//打开文件
std::ifstream in(“enwiki-latest-pages-meta-current1.xml-p000000010p000010000”);
如果(!in)
{

std::没问题,但你应该阅读。一次读一行可能比一个字符好。我假设你是在优化版本中测试这个,如果不是的话,你应该这样做。@CaptainObvlious谢谢,修复了。你试过
split
然后
fgrep
?你的瓶颈应该是文件输入。为了提高性能,more数据应该在搜索之前读取到内存中。一个线程将数据读入缓冲区,另一个线程搜索缓冲区,可能会获得一些额外的性能。谢谢,读取行是一个巨大的改进。我现在得到115mb/s。但是,这仍然只会导致硬盘激活时间的25%。你说的大量行是什么意思?我相信整个Wikipedia文章都存储在一行中。当一行比可用缓存大时,它将被读入缓存,被逐出,然后带回来寻找字符串。如果有时发生这种情况并不好,但如果频繁发生则不好。如果该行甚至比可用的主内存大,它甚至会被删除e从内存中退出并读取多次。对于典型长度为几kB的行,这可能没问题,对于只有几MB的行,这可能已经不好了。我不能只读取固定大小的块,这样行长度就不再重要了吗?我可以像任何其他字符一样读取固定大小的块。当然,您可以读取固定大小的块。但是,请记住,您需要检查是否有拆分为两页的
。此外,这样做还需要将缓冲区复制到新位置。使用
std::find()
使用
std::istreambuf_迭代器
可以在流缓冲区的缓冲区中定位起始字符,从而避免额外的副本。很好,检查拆分为两个块的令牌对我的算法没有问题。我只需在块之间保持
匹配的值。固定大小的块给我140mb/s和90%的disk活动时间。我想这是我能做到的,或者你有其他想法吗?
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;
}
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;
    }
}