我开始使用unordered_set
命名空间中的tr1
类来加速对普通(基于树的)STL map
的访问。但是,我想在boost(boost::thread::id
)中存储对线程ID的引用,并意识到这些标识符的API是如此不透明,以至于您无法清楚地获得它的哈希值。
令人惊讶的是,boost实现了tr1
的部分内容(包括hash
和unordered_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>
类?
感谢。
答案 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.hpp
以size_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和某些东西(例如:整数)之间进行映射的类,您可以将其用作散列。唯一的缺点是你必须确保系统中只有一个映射对象实例。