【问题标题】:Create, access, store and load boost::bimap in an efficient way以高效的方式创建、访问、存储和加载 boost::bimap
【发布时间】:2017-12-13 17:13:24
【问题描述】:

继续my earlier question to serialize bitsets以避免在相同数据上重复创建bimap,因此请保存bimap并在需要时加载。

我选择boost::bimap 将数据(以位集)存储在<key,value> 对中,因为它使用散列技术并且需要O(1) 操作来搜索。 bimap 可能有 4000 万个位集条目,并希望执行以下操作。

  1. 在尽可能短的时间内在bimap 中插入位集。 Answer to my earlier question takes more time(与 2 下给出的散列函数相比,25 万个位集条目接近 5 秒,是 5 倍)。出于同样的原因,使用了unordered_set_ofunordered_multiset_of

  2. 我希望bimap 消耗尽可能少的内存并避免复制,这与下面的哈希函数不同。

    namespace std {
        template <typename Block, typename Alloc>
        struct hash<boost::dynamic_bitset<Block, Alloc> > {
    
            using bitset_type = boost::dynamic_bitset<Block, Alloc>;
            using block_type = typename bitset_type::block_type ;
    
            size_t operator()(boost::dynamic_bitset<Block, Alloc> const& bs) const
            {
                thread_local static std::vector<block_type> block_data;
                auto blocks = bs.num_blocks();
                block_data.assign(blocks, 0);
                to_block_range(bs, block_data.begin());
                return boost::hash<std::vector<block_type>>()(block_data);
            }
        };
    }
    
  3. O(1) 搜索键/值。

  4. 在短时间内加载 bimap。 Again, loading bimap takes much time(对于包含 25 万个条目、大小为 12 MB 的双图,需要将近 20 秒)。

所以我想实现my already asked question的1、2、3和4,答案代码@sehe如下所示。

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/bimap.hpp>
#include <boost/bimap/unordered_multiset_of.hpp>
#include <boost/bimap/unordered_set_of.hpp>
#include <boost/dynamic_bitset/serialization.hpp>
#include <fstream>
#include <iostream>
#include <string>

#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/stream.hpp>

#include <boost/functional/hash.hpp>

namespace serial_hashing { // see https://stackoverflow.com/questions/30097385/hash-an-arbitrary-precision-value-boostmultiprecisioncpp-int
    namespace io = boost::iostreams;

    struct hash_sink {
        hash_sink(size_t& seed_ref) : _ptr(&seed_ref) {}

        typedef char         char_type;
        typedef io::sink_tag category;

        std::streamsize write(const char* s, std::streamsize n) {
            boost::hash_combine(*_ptr, boost::hash_range(s, s+n));
            return n;
        }
      private:
        size_t* _ptr;
    };

    template <typename T> struct hash_impl {
        size_t operator()(T const& v) const {
            using namespace boost;
            size_t seed = 0;
            {
                iostreams::stream<hash_sink> os(seed);
                archive::binary_oarchive oa(os, archive::no_header | archive::no_codecvt);
                oa << v;
            }
            return seed;
        }
    };
}

namespace std {
    template <typename Block, typename Alloc> struct hash<boost::dynamic_bitset<Block, Alloc> >
        : serial_hashing::hash_impl<boost::dynamic_bitset<Block, Alloc> > 
    {};
} // namespace std

namespace bimaps = boost::bimaps;
using Bitset = boost::dynamic_bitset<>;

typedef boost::bimap<
    bimaps::unordered_set_of<Bitset, std::hash<Bitset> >,
     bimaps::unordered_multiset_of<Bitset, std::hash<Bitset> > > Index;

int main() {
    using namespace std::string_literals;

    {
        std::cout << "# Writing binary file ... " << std::endl;
        Index index;
        index.insert({Bitset("10010"s), Bitset("1010110110101010101"s)});

        std::ofstream ofs("binaryfile", std::ios::binary);
        boost::archive::binary_oarchive oa(ofs);
        oa << index;
    }

    {
        std::cout << "# Loading binary file ... " << std::endl;
        std::ifstream ifs("binaryfile", std::ios::binary); // name of loading file

        boost::archive::binary_iarchive ia(ifs);

        Index index;
        ia >> index;
    }
}

编辑

目标 我有一个真实的例子,我有一个大字符串,例如 2000 或更多百万个字符,例如 40-1 亿个长度为 200 或更多字符的短字符串。我的目标是在大字符串中搜索这些短字符串。我想为大字符串创建bimap的位集,然后在bimap中搜索短字符串。我还想过使用unordered 来快速插入和搜索,这与ordered 不同。

密钥位集长度约为 3-40(一次所有组合)。

值位集长度在 100-2000 左右(一次只有一个,例如如果它是 100,那么所有值条目将在 90-110 左右)。

【问题讨论】:

  • 我的哈希实现只是扩展更好。您从未提到对位集大小的任何限制。因此,我们不能假设存在限制。然而,对于小尺寸,显然更简单的散列函数要快得多。如果它更快,您为什么想使用它? (推论:告诉我们你在做什么,一次,所以我们可以告诉你(again)使用std::bitset&lt;64&gt;或类似的!)。那是 >2 个月前的事了。
  • 我担心bimap 的空间,您在前面的answer 中也提到“您正在复制每个键/值哈希上的所有块”。

标签: c++ serialization boost boost-bimap


【解决方案1】:

您根据带有位集的无序映射来构建整个问题。 目标是什么?你用这个设计模拟了哪些现实生活中的问题?

您的 bitset 有多大?大小的差异是什么?某些比特子集中的分布是什么?假设某些事情比这种通用方法(请参阅下面的 ¹ 和 ²)

您将希望减少分配,将“打包”数据加载到非基于节点的容器中,控制元素何时排序(而不是一直携带该不变量)。

将此类容器放入 Boost Interprocess 共享内存段/内存映射文件中,我取得了出色的成绩。

基准

我使用以下代码对生成/保存/加载数据进行基准测试。

请注意,这没有实现上述任何建议,除了它选择退出哈希表选项。每次插入或查找密钥时不必实例化存档将有很大帮助。另外,请记住,当达到负载因子时,哈希表会重新哈希。调整对于它们真正顺利运行至关重要。

Live On Wandbox

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/bimap.hpp>
#include <boost/bimap/multiset_of.hpp>
#include <boost/dynamic_bitset/serialization.hpp>
#include <fstream>
#include <vector>
#include <random>
#include <chrono>
#include <iostream>

namespace bimaps = boost::bimaps;
using Block = uint32_t;
using Bitset = boost::dynamic_bitset<Block>;

typedef boost::bimap<bimaps::set_of<Bitset>, bimaps::multiset_of<Bitset>> Index;

template <typename Caption, typename F>
auto timed(Caption const& task, F&& f) {
    using namespace std::chrono;
    using namespace std::chrono_literals;
    struct _ {
        high_resolution_clock::time_point s;
        Caption const& task;
        ~_() { std::cout << " -- (" << task << " completed in " << (high_resolution_clock::now() - s) / 1.0s << "s)\n"; }
    } timing { high_resolution_clock::now(), task };

    return f();
}

int main(int argc, char**) {
    using namespace std::string_literals;

    auto gen_bitset = [
        data=std::vector<Block>(64), // max 2048 bits
        prng=std::mt19937{42} // { std::random_device{}() }
    ]() mutable {
        auto length_gen = std::uniform_int_distribution<size_t>(data.size()/2, data.size());
        auto block_gen = std::uniform_int_distribution<Block>{};

        size_t n = length_gen(prng);
        std::generate_n(data.begin(), n, [&]{ return block_gen(prng); });

        return Bitset(data.begin(), data.begin()+n);
    };

    if (argc>1) {
        std::cout << "# Creating ... " << std::endl;
        Index index;

        timed("Generating data set", [&] {
            for (size_t i = 0; i < 52<<19; ++i) {
                index.insert({gen_bitset(), gen_bitset()});
            }
        });

        timed("Writing binary file", [&] {
            std::ofstream ofs("binaryfile", std::ios::binary);
            boost::archive::binary_oarchive oa(ofs);
            oa << index;
        });
        std::cout << "Written " << index.size() << " key/value pairs\n";
    } else {
        std::cout << "# Loading ... " << std::endl;
        Index index;

        timed("Loading binary file", [&] {
            std::ifstream ifs("binaryfile", std::ios::binary); // name of loading file
            boost::archive::binary_iarchive ia(ifs);
            ia >> index;
        });

        std::cout << "Roundtripped " << index.size() << " key/value pairs\n";
    }
}

这将创建一个包含 27262976 个键/值对的 11G 文件。所有的键和值都是均匀随机的位集,长度均匀分布在 1024..2048 位之间。

 rm binaryfile                                                                    
 time ./sotest 1                                                                  
     -- (Generating data set completed in 228.499s)
     -- (Writing binary file completed in 106.083s)
    Written 27262976 key/value pairs

    real    5m48.362s
    user    5m32.876s
    sys     0m14.704s
 ls -ltrah binaryfile 
    -rw-rw-r-- 1 sehe sehe 11G dec 14 01:16 binaryfile
 time ./sotest
    # Loading binary file ... 
     -- (Loading binary file completed in 135.167s)
    Roundtripped 27262976 key/value pairs

    real    2m19.397s
    user    2m11.624s
    sys     0m7.764s

当将数据集减少到 25 万个条目时,我得到一个 106MiB 的文件¹,以及以下时间:

 rm binaryfile 
 time ./sotest 1
    # Creating ... 
     -- (Generating data set completed in 1.13267s)
     -- (Writing binary file completed in 0.586325s)
    Written 262144 key/value pairs

    real    0m1.825s
    user    0m1.676s
    sys     0m0.140s
 ls -ltrah binaryfile 
    -rw-rw-r-- 1 sehe sehe 106M dec 14 01:44 binaryfile
 time ./sotest
    # Loading ... 
     -- (Loading binary file completed in 0.776928s)
    Roundtripped 262144 key/value pairs

    real    0m0.823s
    user    0m0.764s
    sys     0m0.056s

¹这基本上告诉我您的位集要小得多 - 我认为这可能强烈有利于其他数据结构选择

² 我注意到旧的非缩放哈希实现是由 Richard Hodges in an older answer 编写的。你明白发生了什么吗?您要求 X,但提供的信息太少,以至于人们无法真正了解您的问题,因此您得到了 X 的答案。但这并不是最优的。你真正的问题是别的。

StackOverflow 可能拥有最优秀的程序员,但他们不会神奇地看穿您的X/Y problems,即使他们可能会闻到它并试图将其引出。最后,我们不是通灵者。与可以指导您完成每一步的资深导师/同事一起工作是无可替代的。

【讨论】:

  • 我会等待 [原文如此,nomen est omen] 进一步澄清,然后再花更多时间在这里。无论如何,很明显,性能不一定来自复杂性(事实上,它很少这样做),理论上“更好”的资源复杂性并不能保证实践中的更好行为。你必须真正地​​知道你需要什么,你最终要做什么以及为什么。
  • 谢谢,请检查上面的编辑,我希望它改善了问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-09
  • 1970-01-01
相关资源
最近更新 更多