tr1 :: hash for boost :: thread :: id?

时间:2009-04-21 11:49:46

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

我开始使用unordered_set命名空间中的tr1类来加速对普通(基于树的)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<boost::thread::id>效率低。

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

感谢。

5 个答案:

答案 0 :(得分:8)

字符串化thread::id(仅用于计算后面的字符串哈希值)的开销,正如您几乎所说的那样,天文数字与tr1::unordered_map可能赋予的std::map相比具有任何性能优势。 1}}。所以简短的回答是:坚持使用std :: map&lt; thread :: id,...&gt;

如果绝对必须使用无序容器,尽可能使用native_handle_type 而不是thread::id,即更喜欢tr1::unordered_map< thread::native_handle_type, ... > ,在thread::native_handle()thread::get_id()时调用insert而不是find

请勿尝试以下

之类的内容
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.hppsize_t hash_value(const id& tid)作为thread::id的朋友添加{{1}}也是“更好”。 :)

答案 1 :(得分:3)

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

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

正如KeithB所建议的那样(我不会评论使用二进制表示,因为没有任何东西可以保证2个ID在所有情况下具有相同的二进制表示...),使用排序的vector可以加速代码以防万一项目很少。

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

然而,有一种数据结构试图将地图和有序向量的好处联系起来:B+Tree

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

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

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

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

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

答案 2 :(得分:2)

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

如果搜索比添加和删除更频繁,您可以使用排序向量。有一个&lt;为boost :: thread :: id定义的运算符,因此您可以在每次添加或删除后对向量进行排序(或插入到正确的位置),并使用lower_bound()进行二进制搜索。这与搜索集合的复杂性相同,并且对于少量数据应该具有较低的开销。

如果你仍然需要这样做,那么将它作为sizeof(boost :: thread:id)字节处理,然后对它们进行操作。

此示例假设boost :: thread :: id的大小是int大小的倍数,并且没有打包,也没有虚函数。如果不是这样,则必须对其进行修改,否则将无法正常工作。

编辑:我看了一下boost::thread::id类,它有一个boost::shared_pointer<>作为成员,所以下面的代码被严重破坏了。我认为唯一的解决方案是让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];

答案 3 :(得分:2)

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

反而有时候提升有一个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库。

答案 4 :(得分:0)

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