【问题标题】:tr1::hash for boost::thread::id?tr1::hash for boost::thread::id?
【发布时间】:2024-06-03 11:25:02
【问题描述】:

我开始使用tr1 命名空间中的unordered_set 类来加速对普通(基于树的)STL map 的访问。但是,我想在 boost (boost::thread::id) 中存储对线程 ID 的引用,并意识到这些标识符的 API 是如此不透明,以至于您无法清楚地获得它的哈希值。

令人惊讶的是,boost 实现了 tr1 的一部分(包括 hashunordered_set),但它没有定义能够散列线程 ID 的散列类。

查看boost::thread::id 的文档,我发现线程 ID 可以输出到流中,所以我的哈希解决方案是:

struct boost_thread_id_hash
{
    size_t operator()(boost::thread::id const& id) const
    {
        std::stringstream ostr;
        ostr << id;
        std::tr1::hash<std::string> h;
        return h(ostr.str());
    }
};

即对其进行序列化,将哈希应用于结果字符串。但是,这似乎比实际使用 STL map&lt;boost::thread::id&gt; 效率低。

所以,我的问题是:您找到更好的方法了吗?不强制 hash&lt;boost::thread::id&gt; 类的存在是否在 boost 和 tr1 中存在明显的不一致?

谢谢。

【问题讨论】:

    标签: c++ boost hash boost-thread unordered-set


    【解决方案1】:

    字符串化thread::id 的开销(仅在之后计算字符串哈希)就像您自己所说的那样,与tr1::unordered_map 相对于std::map 可能带来的任何性能优势相比是天文数字。所以简短的回答是:坚持使用 std::map

    如果您绝对必须使用无序容器,如果可能,请尝试使用native_handle_type而不是thread::id,即更喜欢tr1::unordered_map&lt; thread::native_handle_type, ... &gt;,而是调用thread::native_handle() thread::get_id() inserting 和 finding。

    请勿尝试以下操作

    struct boost_thread_id_hash {
       // one and only member of boost::thread::id is boost::thread::id::thread_data
       //   of type boost::detail::thread_data_ptr;
       // boost::thread::id::operator==(const id&) compares boost::thread::id::thread_data's
       size_t operator()(boost::thread::id const& id) const {
          const boost::detail::thread_data_ptr* pptdp = \
            reinterpret_cast< boost::detail::thread_data_ptr* >(&id);
          return h(pptdp->get());
       }
    };
    

    它可以工作,但非常脆弱,几乎可以保证是定时炸弹。它假定您对thread::id 实现的内部工作有深入的了解。它会让你被其他开发者诅咒。如果可维护性有任何问题,请不要这样做!即使修补boost/thread/detail/thread.hpp 以将size_t hash_value(const id&amp; tid) 添加为thread::id 的朋友也“更好”。 :)

    【讨论】:

    • +1,感谢您的回答。事实上,我认为这是最好的,所以我会接受它。我不确定从长远来看,native_handle 和相关的native_handle_type 的“标准”程度如何。似乎thread::id 散列可以包含在提升的合理时间内,因为如果我记得很清楚,有一些报告反对 TR1 也没有它......总而言之:谢谢,我没想到@ 987654339@.
    【解决方案2】:

    显而易见的问题是,您为什么要实际使用哈希?

    我了解map / set 对性能关键代码的问题,实际上这些容器对缓存不是很友好,因为这些项目可能分配在非常不同的内存位置。

    正如 KeithB 所建议的(我不会评论使用二进制表示,因为没有什么能保证 2 个 id 具有相同的二进制表示...),使用排序的 vector 可以加速代码,以防万一项目很少。

    已排序的向量/双端队列对缓存更加友好,但是由于涉及复制,它们在插入/擦除时会遇到 O(N) 复杂性。一旦你达到几百个线程(顺便说一下,从未见过这么多),它可能会受到伤害。

    但是,有一种数据结构试图将映射和排序向量的好处联系起来:B+Tree

    您可以将其视为一个地图,其中每个节点将包含多个元素(按排序顺序)。仅使用叶节点。

    要获得更多性能,您可以:

    • 线性链接叶子:即根缓存指向第一个和最后一个叶子的指针,并且叶子本身相互连接,因此线性行进完全绕过内部节点。
    • 在根中缓存最后访问的叶子,毕竟它很可能也是下一个访问的叶子。

    渐近性能与地图相同,因为它是作为平衡二叉树实现的,但由于值是分组打包的,因此您的代码可以通过常数变得更快。

    真正的困难是调整每个“桶”的大小,您需要为此进行一些分析,因此如果您的实现允许在那里进行一些自定义会更好(因为这将取决于代码所在的架构执行)。

    【讨论】:

      【解决方案3】:

      为什么要将这些存储在一个集合中。除非你做一些不寻常的事情,否则会有少量线程。维护一个集合的开销可能比仅仅将它们放在一个向量中并进行线性搜索要高。

      如果搜索比添加和删除更频繁,您可以使用排序向量。 boost::thread::id 定义了一个 lower_bound() 进行二分查找。这与搜索集合的复杂性相同,并且对于少量数据应该具有较低的开销。

      如果你还需要这样做,不如把它当作一个 sizeof(boost::thread:id) 字节,然后对它们进行操作。

      这个例子假设 boost::thread::id 的大小是 int 大小的倍数,并且没有打包,也没有虚函数。如果不是这样,则必须对其进行修改,或者根本不起作用。

      编辑:我查看了boost::thread::id 类,它有一个boost::shared_pointer&lt;&gt; 作为成员,所以下面的代码被严重破坏了。我认为唯一的解决方案是让boost::thread 的作者添加一个哈希函数。我将留下这个例子,以防它在其他情况下有用。

      boost::thread::id id;
      unsigned* data;
      // The next line doesn't do anything useful in this case.
      data = reinterpret_cast<unsigned *>(&id);
      unsigned hash = 0;
      
      for (unsigned int i = 0; i < sizeof(boost::thread::id)/4; i++)
        hash ^= data[i];
      

      【讨论】:

      • 基思,感谢您的见解。但是,我们在一个库中使用此代码,该库可能最终被不确定数量的线程(数百个)使用,所以我不想让线程索引成为瓶颈。最后,如何确定对于两个不同的 boost::thread::id 对象,它们的 sizeof 会不同?换句话说,使用您建议的 sizeof 无助于识别线程本身。问候,迭戈。
      • 我将添加一个示例以使其清楚。可能有数百个线程的映射更有意义,但我仍然会对其进行基准测试。我会为我的答案添加另一种选择。
      【解决方案4】:

      回答这个问题晚了几年,但是当尝试将 boost::thread::id 作为键放入 std::unordered_map 时,这显示为最相关的问题。在接受的回复中,获取本机句柄是一个很好的建议,但它不适用于 this_thread。

      boost for sometime 有一个 thread::id 的 hash_value,所以这对我来说很好:

      namespace boost {
        extern std::size_t hash_value(const thread::id &v);
      }
      
      namespace std {
        template<>
        struct hash<boost::thread::id> {
          std::size_t operator()(const boost::thread::id& v) const {
            return boost::hash_value(v);
          }
        };
      }
      

      当然需要链接libboost_thread库。

      【讨论】:

        【解决方案5】:

        您可以创建在 thread::id 和某些东西(例如:整数)之间进行映射的类,您可以将其用作哈希。唯一的缺点是您必须确保系统中只有一个映射对象实例。

        【讨论】: