在c ++优化中合并大型二进制文件

时间:2018-06-09 20:32:27

标签: c++ optimization file-io merge

背景: 我编写了一个修改过的adler32校验和例程,它生成64位校验和。 我想检查大小写字母alpha,数字和_区域中的碰撞。 我编写了一个生成器程序,该程序逐步执行此范围内的所有字符串组合,直到给定的最大长度。 它具有最大为4G的已排序二进制文件的输出以及校验和的结果。 4G限制是由于内存大小,虽然较大的文件意味着较少的文件,这加快了合并和检查。 最多6个字节字符串的总数据大小为64 * 64 * 64 * 64 * 64 * 63 = 67,645,734,912个整数或541GB,每个整数8个字节。 这是我的限制因为下一个iteraton会导致文件加起来达到64 * 541GB或超过34 TB。

问题: 我有122个uint64_t的二进制文件,其中大部分都是4GB大小。每个单独的文件都已排序。我需要检查这些文件中是否有任何重复值。

以下代码似乎有效,但估计最多6个字节字符串的校验和需要花费大约35天的时间。任何人都可以想到任何可能更快的优化或替代方法吗? 请注意,我一次不能在内存中完全包含两个以上的文件。

麦克

struct DataItem
{
    uint64_t data;
    ifstream ifs;
    unique_ptr<char[]> buf;

    explicit DataItem(const string &filename)
        : ifs(filename, ifstream::in | ifstream::binary)
    {
        constexpr size_t bufSize = 1'048'576;
        buf = make_unique<char[]>(bufSize);
        ifs.rdbuf()->pubsetbuf(buf.get(), bufSize);
        readNext();
    }

    void readNext()
    {
        if (ifs.is_open() && !ifs.eof())
            ifs.read(reinterpret_cast<char *>(&data), sizeof(uint64_t));
    }

    bool operator<(DataItem const &other)
    {
        return data < other.data;
    }

    bool operator>(DataItem const &other)
    {
        return data > other.data;
    }
};

int main(int argc, char *argv[])
{
    path givenPath;
    vector<DataItem> data;

    if (argc > 1)
        givenPath = path(argv[1]);
    else
        givenPath = path("*.dat");

    auto directory = givenPath;
    directory.remove_filename();
    if (directory.empty())
        directory = path("./");

    auto extension = givenPath.extension();

    for (auto &p : directory_iterator(directory))
        if (p.path().extension() == extension && is_regular_file(p))
            data.emplace_back(p.path().string());

    sort(data.begin(), data.end());

    uint64_t current = data.front().data;
    data.front().readNext();

    int progress = 0, loop = 0;
    while (!data.empty())
    {
        // bubble the new value to resort the data vector
        auto now = data.begin();
        auto next = now + 1;
        while ((next != data.end()) && (*now > *next))
        {
            swap(*now, *next);
            ++now;
            ++next;
        }

        if (current == data.front().data)
            cout << current << '\t' << (current >> 32) << endl;

        current = data.front().data;

        if (data.front().ifs.eof())
            data.erase(data.begin());
        else
            data.front().readNext();

        ++progress;
        if (progress >= 1'000'000)
        {
            {
                progress = 0;
                cerr << '.';
                ++loop;
                if (loop >= 10)
                {
                    loop = 0;
                    cerr << '|';
                }
            }
        }
    }
    return 0;
}

2 个答案:

答案 0 :(得分:0)

当您的日志在文件中时,您不应该在执行期间对它们进行排序。只需在日志中写入哈希值即可。使用哈希值编写日志有助于并行化。确保在开始写入之前收集大块(例如16 MB)。

完成哈希后进行排序。避免冒泡排序,需要O(n ** 2)时间。合并排序非常适合基于文件的排序。它可以很容易地并行化。没有并行化,它需要O(n log(n))时间。

答案 1 :(得分:0)

正如您已经注意到测试散列函数的质量(校验和是其变体)很快变得棘手。

因此,通常在统计上而不是在算法上测试碰撞阻力。一些示例包括spectral testing以查看输出的分布情况以及chi-squared testing以了解输出在输入发生变化时输出如何不可预测。 另请参阅NIST SP 800-22,它定义了对加密哈希函数的一些检查。 当然字典测试也很有用,但只有一定的深度。

另请注意,针对短序列的测试通常不能提供足够的质量证明,因为散列函数在较长序列上的行为可能大不相同。 您至少还应该检查更长但很大程度上相似的数据集不会发生冲突并检查边缘情况,例如当某个内部值变为0或某个其他临界值时(这取决于所测试函数的具体情况)。

话虽如此,你不需要在内存中读取整个文件;由于您的文件已排序,只需一次打开所有文件并保持每个文件的滑动窗口,移动&#34;指针&#34;在阅读和比较条目时前进(每个文件的移动量会有所不同)。虽然有些缓冲会有所帮助。