从文件中读取键 - 值对尽可能快地在C ++(Reading key-value pairs as

2019-10-30 05:50发布

我有这样的数量约为200万行的文件:

2s,3s,4s,5s,6s 100000
2s,3s,4s,5s,8s 101
2s,3s,4s,5s,9s 102

分离部中的第一逗号表示在Omaha扑克结果,而后者是得分的卡一个例子的“价值”。 对我来说,尽可能快地在C ++中读取该文件,但我似乎无法得到它比使用Python中的基本库的简单方法(4.5秒)的速度是非常重要的。

使用Qt框架(QHash和QString的),我是能够读取该文件在释放模式2.5秒。 不过,我不希望有Qt的依赖。 我们的目标是允许快速模拟使用该2万线,即some_container["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万左右。

一些进一步的信息从不同的意见收集:

  • 示例文件: http://pastebin.com/rB1hFViM
  • RAM限制:750MB
  • 初始化时间限制:5秒
  • 每手限制计算时间:0.5秒

Answer 1:

在我看来,有你的代码的两个瓶颈。

1瓶颈

我认为,该文件的阅读是最大的问题出现。 有一个二进制文件是最快的选项 。 您不仅可以与原始的IStream ::在一个单一的读操作(这是非常快)的阵列直接读取它,但该文件在内存中,如果您的操作系统支持的话,你甚至可以映射。 这里是一个链接这是有关如何使用内存映射文件内容非常丰富。


2瓶颈

性病::地图通常与实现自我平衡BST ,将所有的数据存储在顺序。 这使得插入是一个O(logn)时间操作。 你可以改变它到std :: unordered_map,至极使用哈希表来代替。 阿散列表有一个恒定的时间插入如果colisions的数目是低的。 正如你需要读取已知元素的ammount的,你可以保留插入元素之前chuncks合适ammount的。 请记住,你需要比将在哈希插入,以避免colisions的最大ammount的元素个数多chuncks。



Answer 2:

伊恩·梅代罗斯已经提到的两大botlenecks。

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

的不同的卡的量是已知的:每13张牌4种颜色 - > 52张牌。 所以一个卡需要少于6位来存储。 您当前的文件格式,目前使用24位(includig逗号)。 所以通过简单地列举卡和省略逗号,你可以保存文件的大小〜2/3和允许你确定卡读取每张卡只有一个字符。 如果你想保持基于您可以使用时该文件的文本,新西兰,AM和新西兰的四种颜色。

另一件事是我的错误是基于字符串的地图。 字符串操作是innefficient。 一方面包含5张牌。 这意味着52 ^ 5 posiibilities如果我们保持它的简单和不考虑已绘制的卡片。

- > 52 ^ 5 = 380.204.032 <2 ^ 32

这意味着我们可以enumuerate每一个可能的手带UINT32号码。 通过定义的卡一个特殊的分类方法(因为顺序无关),我们可以将号码分配给手,使用该号码作为在我们的地图比使用字符串快了很多关键。

如果我们有足够的内存(1.5 GB),我们甚至不需要一个地图,我们可以简单地使用数组。 当然大多数细胞是未使用,但访问可能是非常快的。 我们因为细胞存在independet如果我们填写与否甚至可以ommit卡片的顺序。 所以我们可以使用它们。 但在这种情况下,你不应该忘记填写从文件中读取手的所有可能的排列。

这个方案我们也(可能)可以进一步优化我们的文件的阅读速度。 如果我们只存储手数和等级,以便只有2值需要进行解析。

INFACT我们可以通过使用用于不同的手更复杂adressing方案优化所需的存储空间,因为在现实中有仅52 * 51 * 50 * 49 * 48 = 311.875.200可能hands.additional于所述顺序是无关紧要提及,但我认为这节省是不值得的手编码复杂程度的提高。



Answer 3:

一个简单的想法可能是使用C API,这是相当简单:

#include <cstdio>

int n;
char s[128];

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

一个粗略的测试表现出了相当大的加速,我比Iostreams库。

进一步的加速比可通过存储在一个连续的数组中的数据,例如一个向量来实现std::pair<std::string, int> ; 这取决于你的数据是否已经排序,以及如何需要在以后访问它。

对于一个严肃的解决方案,但是,你应该退后一步进一步,并认为更好的方式来表示你的数据。 例如,固定宽度,二进制编码会更节省空间的和更快的解析,因为你不会需要向前看的行尾或解析字符串。

更新:从一些简单的实验,我发现它相当快的,首先将整个文件读入内存,然后执行交替strtok与任何电话" ""\n"作为分隔符; 每当一对呼叫的成功,应用strtol在第二指针解析整数。 这里有一个骨架:

#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"
        }
    }
}


文章来源: Reading key-value pairs as fast as possible in C++ from file