如何用标记读取64bit int文件最快?

时间:2014-05-06 00:15:56

标签: c++ c++11 fstream

我试图从文件中读取一些数据来填充以下数据结构:

typedef uint64_t t_feat; 

typedef pair<vector<t_feat>, int> t_featfam;

每个日志文件都包含几个这样的系列,所以我想将它们全部保存在矢量中。 具有非常简单格式的日志文件:

line = "-": start a new family

line = "#": family ends here

line = 64bit unsigned integer (as string): add this value to the family

line = "!": mark the following integer as important (exactly one is marked like this in each family), the marking is done by setting the the second value of the family to the index of the important element

文件中没有错误,因此每个!后跟一个整数,所有系列都正常启动和结束,没有额外的空格或任何东西(只有异常是文件末尾可能的空行) )。

现在我使用以下代码:

void read_data_from_file(const string &fname, vector<t_featfam> &data)
{
    ifstream f;
    f.open(fname, ios::in);
    while (!f.eof())
    {
        string currentline;
        getline(f, currentline);
        if (currentline == "" || currentline == "#")
            continue;
        else if (currentline == "-")
            data.push_back(t_featfam());
        else if (currentline == "!")
            data.back().second = data.back().first.size();
        else
        {
            istringstream iss(currentline);
            t_feat value;
            iss >> value;
            data.back().first.push_back(value);
        }
    }
}

这很有效,但感觉非常低效,可能是...... 如果它只是数字,我肯定只使用fstreams,但实际上,我不确定如何正确地做到这一点。任何人都可以向我暗示正确的方向吗?这应该是可能的。我使用的是Visual Studio,并不介意特定于VS的解决方案,但不想包括提升。

EDIT2: 现在是一个真正有效的版本,使用史蒂夫代码,并通过luk32的想法改进... 比上面的代码快4倍......

void read_data_from_file(const string &fname, vector<t_featfam> &data)
{
    ifstream f;
    f.open(fname, ios::in);
    char* currentline = new char [30];
    while (!f.eof())
    {
        f.getline(currentline, 30);
        switch (currentline[0])
        {
        case '\0':
        case '#':
            break;
        case '-':
            data.push_back(t_featfam());
            break;
        case '!':
            data.back().second = data.back().first.size();
            break;
        default:
            data.back().first.push_back(stoull(currentline));
            break;
        }
    }
    delete currentline;
}

3 个答案:

答案 0 :(得分:1)

我可能会按照以下方式做点什么:

  • currentline移到循环外部 - 防止每次循环时重新分配
  • char currentline的第一个if/else上使用switch语句,因此我们跳转而不是多个std::stoull语句
  • 使用stringstream代替currentlineuint64_t转换为void read_data_from_file(const string &fname, vector<t_featfam> &data) { ifstream f; f.open(fname, ios::in); string currentline; while (!f.eof()) { getline(f, currentline); switch (currentline.c_str()[0]) { case '\0': case '#': break; case '-': data.push_back(t_featfam()); break; case '!': data.back().second = data.back().first.size(); break; default: data.back().first.push_back(std::stoull(currentline)); break; } } }

这是函数(没有经过测试,看它是否编译,只是编写了它)

{{1}}

答案 1 :(得分:1)

大部分时间都在内存分配中丢失。当您构建getline()时,您有一个分配,而当构建istringstream时,您有另一个分配。每个分配在我的系统上花费大约250个周期。因此,您每行可以节省大约500个周期。

如果您使用mmap()将整个文件映射到地址空间,则可以完全取消分配。一旦将所有内容放在char的单个大型数组中,就可以相对轻松地解析它,而无需从该大型数组中复制行。

答案 2 :(得分:0)

处理整个文件需要多长时间?如果速度确实是一个问题,并且因为如果你在PC上运行这个程序你应该有足够的内存,你可以使用相当于seek来结束,告诉,寻求开始获得整个日志的大小文件,分配那么多内存,然后将整个文件读入一个大缓冲区。然后使用memchr()扫描每次出现的“ - ”,以确定对的数量,可选地创建一个指针数组(根据给定文件大小的最大对数预分配)然后执行一次调整大小成对矢量(如果使用指向矢量对的指针,则为一次新的)。然后再次解析缓冲区以通过索引或迭代器而不是push_back()填充对。虽然此方法扫描文件缓冲区两次,但可以通过避免在一堆push_backs()中发生的内部动态调整来进行补偿。

另一种选择是将对数计数放在日志文件的开头或结尾,这将消除用于获得对数的第一次扫描。如果你知道最大文件大小,你可以只分配足够的内存来处理最大的预期日志文件,这样就无需在分配之前确定文件大小。