从文件中快速读取C++中的键值对

从文件中快速读取C++中的键值对,c++,hashmap,containers,C++,Hashmap,Containers,我有一个大约有200万行的文件,如下所示: 2s,3s,4s,5s,6s 100000 2s,3s,4s,5s,8s 101 2s,3s,4s,5s,9s 102 第一个逗号分隔的部分表示奥马哈的扑克结果,而后一个分数是这些卡的示例值。对我来说,在C++中尽可能快地读取这个文件是非常重要的,但是我无法用Python 4.5秒使用一个简单的方法来使用它。p> 使用Qt框架QHash和QString,我能够在发布模式下在2.5秒内读取文件。但是,我不希望有Qt依赖关系。目标是允许使用这200万行进

我有一个大约有200万行的文件,如下所示:

2s,3s,4s,5s,6s 100000
2s,3s,4s,5s,8s 101
2s,3s,4s,5s,9s 102
第一个逗号分隔的部分表示奥马哈的扑克结果,而后一个分数是这些卡的示例值。对我来说,在C++中尽可能快地读取这个文件是非常重要的,但是我无法用Python 4.5秒使用一个简单的方法来使用它。p> 使用Qt框架QHash和QString,我能够在发布模式下在2.5秒内读取文件。但是,我不希望有Qt依赖关系。目标是允许使用这200万行进行快速模拟,即一些_容器[2s、3s、4s、5s、6s]可以产生100,但如果应用翻译功能或任何不可读的格式,也可以实现更快的读取

我当前的实现速度非常慢8秒!:

std::map<std::string, int> get_file_contents(const char *filename)
{
    std::map<std::string, int> outcomes;
    std::ifstream infile(filename);

    std::string c;
    int d;

    while (infile.good())
    {
        infile >> c;
        infile >> d;
        //std::cout << c << d << std::endl;
        outcomes[c] = d;
    }
    return outcomes;
}
我该怎么做才能尽可能快地将这些数据读入某种键/值散列

注意:前16个字符总是会出现在卡片上,而分数可以高达100万左右

从各种评论中收集的一些进一步信息:

示例文件: 内存限制:750MB 初始化时间限制:5s 每只手的计算时间限制:0.5s
一个简单的想法可能是使用C API,这相当简单:

#include <cstdio>

int n;
char s[128];

while (std::fscanf(stdin, "%127s %d", s, &n) == 2)
{
    outcomes[s] = n;
}

在我看来,代码上有两个瓶颈

1瓶颈

我认为文件读取是那里最大的问题。您不仅可以使用原始的istream::read在一个数组中直接读取它,而且在一个操作中读取速度非常快,如果您的操作系统支持,您甚至可以将文件映射到内存中。这是一个关于如何使用内存映射文件的非常有用的例子

2瓶颈


映射通常使用一个将按顺序存储所有数据的函数来实现。这使插入成为一个操作。您可以将其更改为std::unordered_映射,它使用一个。如果合并数较低,则具有恒定的插入时间。由于您需要读取的元素数量是已知的,因此您可以在插入元素之前选择合适的元素数量。请记住,您需要比将插入哈希中的元素数量更多的chunck,以避免最大数量的合并。

Ian Medeiros已经提到了两个主要的Botleneck

关于数据结构的一些想法:

不同卡的数量是已知的:13张卡中有4种颜色->52张卡。 所以一张卡需要少于6位来存储。您当前的文件格式当前使用24位逗号。 因此,通过简单地枚举卡片并省略逗号,您可以节省约2/3的文件大小,并允许您在每张卡片上只读取一个字符的情况下确定一张卡片。 如果你想保持文件文本的基础上,你可以使用a-m,n-z,a-m和n-z的四种颜色

另一件让我头疼的事情是基于字符串的映射。字符串操作非常高效。 一只手包含5张牌。 这意味着如果我们保持简单并且不考虑已经绘制的卡片,则有52 ^ 5的可能性。

->52^5=380.204.032<2^32

这意味着我们可以用uint32数字枚举每一只可能的手。通过定义一个特殊的卡片排序方案,因为顺序是不相关的,我们可以给手分配一个数字,并在地图中使用这个数字作为键,这比使用字符串快得多

如果我们有足够的1.5GB内存,我们甚至不需要一个映射,但我们可以简单地使用一个数组。 当然,大多数单元格未使用,但访问速度可能非常快。我们甚至可以控制卡片的顺序,因为如果我们填充或不填充,单元格是独立存在的。所以我们可以使用它们。但在这种情况下,您不应该忘记填写从文件中手动读取的所有可能排列

使用此方案,我们还可以进一步优化文件读取速度。如果我们只存储手数和评级,那么只需要解析2个值


事实上,我们可以通过为不同的手使用更复杂的寻址方案来优化所需的存储空间,因为实际上只有52*51*50*49*48=311.875.200只可能的手。除此之外,如前所述,排序是不相关的,但我认为这种节省不值得增加手部编码的复杂性。

如果快速访问是一个如此重要的问题,为什么要将这些数据存储为文本?@KerrekSB我不知道不幸的是,替代品。注意这是一场友谊赛;解决方案必须是独立的,并且不能连接到数据库。因此,文件生成超出了您的控制范围?在这种情况下,您可以使用的最快的数据结构是带有预先分配的chuncks的无序_映射。如果可以,请将数据存储在固定宽度的二进制表示形式中。@PascalvKooten:作为一名程序员,无需学习CS。我建议看一下。是的,文件中的二进制表示形式已经是可搜索和排序的格式
如果允许的话,这条路该走了。注意。如果您最终使用vector,请确保保留所需的大小,因为调整vector的大小需要大量数据移动,并且是性能杀手。为了实现这一目标,我将在稍后运行模拟,以便了解我的卡,我将随机抽取许多敌手,减去我已经持有的牌,以确定谁在桌上的牌中得分较高。我不确定在这里对值进行排序是否有帮助,因为根据我拥有的卡片,向量中的某个地方会有间隙?而且,我对向量对有点困惑。在向量中搜索密钥会很慢?@PascalvKooten:嗯,在地图中存储数据比在向量中存储数据要慢,但是你可以通过快速查找按顺序获得数据。相比之下,通过字符串比较对包含数百万个元素的向量进行排序可能代价高昂。因此,请选择最适合您的访问模式的表示法。@PascalvKooten:对于未排序的向量,您必须进行线性搜索,而且由于您正在进行字符串比较,因此需要使用大量随机内存。当然,真正的解决方案不是使用字符串键。谢谢。我只是想知道,既然文件应该以键、值组合的形式读取,那么如何一次读取?这听起来与向量无关?将它映射到内存,然后从那里填充无序的_映射。磁盘->内存传输是您最大的问题。我的意思是:您将从内存中的数组中读取内容,或者直接从std:istream中读取内容。使用std::ifstream解析文本会非常慢。很好地解决了整个问题。我认为如果他围绕所有给出的答案进行研究,他将得到一个非常好的实现。很抱歉在后面提到这一点,但对于整个实现,我的限制是使用750mb的ram来运行整个程序。因此,让我们考虑600 MB RAM最大。
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>

int main()
{
    std::vector<char> data;

    // Read entire file to memory
    {
        data.reserve(100000000);

        char buf[4096];
        for (std::size_t n; (n = std::fread(buf, 1, sizeof buf, stdin)) > 0; )
        {
            data.insert(data.end(), buf, buf + n);
        }
        data.push_back('\0');
    }

    // Tokenize the in-memory data
    char * p = &data.front();
    for (char * q = std::strtok(p, " "); q; q = std::strtok(nullptr, " "))
    {
        if (char * r = std::strtok(nullptr, "\n"))
        {
            char * e;
            errno = 0;
            int const n = std::strtol(r, &e, 10);
            if (*e != '\0' || errno != 0) { continue; }

            // At this point we have data:
            // * the string is "q"
            // * the integer is "n"
        }
    }
}