我编写了一个类来充当顺序容器(std::vector/ std::queue/ std::list)的包装,以具有a的接口std::map,以在使用少量小对象时提高性能。考虑到已经存在的算法,编码都非常简单。此代码显然是 高度 从我的全码修剪,但显示的问题。
std::vector
std::queue
std::list
std::map
template <class key_, class mapped_, class traits_ = std::less<key_>, class undertype_ = std::vector<std::pair<key_,mapped_> > > class associative { public: typedef traits_ key_compare; typedef key_ key_type; typedef mapped_ mapped_type; typedef std::pair<const key_type, mapped_type> value_type; typedef typename undertype_::allocator_type allocator_type; typedef typename allocator_type::template rebind<value_type>::other value_allocator_type; typedef typename undertype_::const_iterator const_iterator; class value_compare { key_compare pred_; public: inline value_compare(key_compare pred=key_compare()) : pred_(pred) {} inline bool operator()(const value_type& left, const value_type& right) const {return pred_(left.first,right.first);} inline bool operator()(const value_type& left, const key_type& right) const {return pred_(left.first,right);} inline bool operator()(const key_type& left, const value_type& right) const {return pred_(left,right.first);} inline bool operator()(const key_type& left, const key_type& right) const {return pred_(left,right);} inline key_compare key_comp( ) const {return pred_;} }; class iterator { public: typedef typename value_allocator_type::difference_type difference_type; typedef typename value_allocator_type::value_type value_type; typedef typename value_allocator_type::reference reference; typedef typename value_allocator_type::pointer pointer; typedef std::bidirectional_iterator_tag iterator_category; inline iterator(const typename undertype_::iterator& rhs) : data(rhs) {} inline reference operator*() const { return reinterpret_cast<reference>(*data);} inline pointer operator->() const {return reinterpret_cast<pointer>(structure_dereference_operator(data));} operator const_iterator&() const {return data;} protected: typename undertype_::iterator data; }; template<class input_iterator> inline associative(input_iterator first, input_iterator last) : internal_(first, last), comp_() {if (std::is_sorted(internal_.begin(), internal_.end())==false) std::sort(internal_.begin(), internal_.end(), comp_);} inline iterator find(const key_type& key) { iterator i = std::lower_bound(internal_.begin(), internal_.end(), key, comp_); return (comp_(key,*i) ? internal_.end() : i); } protected: undertype_ internal_; value_compare comp_; };
SSCCE位于http://ideone.com/Ufn7r,完整代码位于http://ideone.com/MQr0Z(注意:IdeOne的生成时间非常不稳定,可能是由于服务器负载所致,并且没有清楚地显示有问题的结果)
我使用进行了测试std::string,并使用MSVC10对POD从4到128字节(范围从8到2000个元素)进行了测试。
std::string
我期望在(1)从一定范围内为小对象创建,(2)对少量小对象进行随机插入/擦除以及(3)对所有对象进行查找方面具有更高的性能。出乎意料的是,根据在所有测试范围内创建的向量,向量 显着 更快,而对于最大约2048个字节(512个4字节对象或128个16字节对象等)的大小的随机擦除,向量的创建速度则更快。但是,最令人震惊的是,std::vector使用std::lower_bound速度比std::map::find所有POD 都要慢。对于4字节和8字节的POD,差异很小,但是对于128字节的POD,差异std::vector要慢36%!但是,对于std::string,std::vector平均速度要快6%。
std::lower_bound
std::map::find
我感觉由于更好的缓存局部性/较小的内存大小,std::lower_bound排序后的std::vector表现应该会跑赢大盘std::map,而且由于map可能无法完美平衡,或者在 最坏的情况下 它应该 匹配 std::map,但我一生都无法想到std::map应该更快。我唯一的想法是谓词会以某种方式降低它的速度,但是我不知道怎么做。所以,问题是: 这怎么可能是std::lower_bound在一个排序std::vector可以通过跑赢std::map(在MSVC10)?
map
[编辑]我已经证实,std::lower_bound在std::vector<std::pair<4BYTEPOD,4BYTEPOD>>使用较少的 比较 平均比std::map<4BYTEPOD,4BYTEPOD>::find(由0-0.25),但我实现仍然高达26%速度较慢。
std::vector<std::pair<4BYTEPOD,4BYTEPOD>>
std::map<4BYTEPOD,4BYTEPOD>::find
[POST-ANSWER- EDIT]我在http://ideone.com/41iKt上创建了SSCCE,该SSCCE 删除了所有不需要的绒毛,并清楚地表明,find排序后的绒毛vector比慢map,约15%。
find
vector
这是一个更有趣的螺母!在到目前为止讨论我的发现之前,让我指出该associative::find()函数的行为不同于std::map::find():如果未找到键,则前者返回下限,而后者返回end()。要解决此问题,associative::find()需要将其更改为以下形式:
associative::find()
std::map::find()
end()
auto rc = std::lower_bound(this->internal_.begin(), this->internal_.end(), key, this->comp_); return rc != this->internal_.end() && !this->comp_(key, rc->first)? rc: this->internal_.end();
现在,我们更有可能将苹果与苹果进行比较(我现在还没有验证逻辑是否真的正确),让我们继续研究性能。我不太相信用来测试性能的方法确实可以保持水准,但是我现在坚持使用它,我肯定可以提高associative容器的性能。我认为我没有在代码中找到所有性能问题,但至少取得了一些进展。最大的注意是,中使用的比较功能associative非常糟糕,因为它会不断制作副本。这使该容器有些不利。如果您现在正在检查比较器,则可能看不到它,因为它看起来好像该比较器 是 通过引用传递!这个问题实际上是非常微妙的:基础容器具有value_type,std::pair<key_type, mapped_type>但是比较器将其std::pair<key_type const, mapped_type>作为参数!修复此问题似乎可以使关联容器大大提高性能。
associative
value_type
std::pair<key_type, mapped_type>
std::pair<key_type const, mapped_type>
为了实现一个比较器类,它没有机会无法完全匹配参数,我使用了一个简单的帮助程序来检测类型是否为std::pair<L, R>:
std::pair<L, R>
template <typename> struct is_pair { enum { value = false }; }; template <typename F, typename S> struct is_pair<std::pair<F, S>> { enum { value = true }; };
…然后我用一个稍微复杂一些的比较器替换了比较器:
class value_compare { key_compare pred_; public: inline value_compare(key_compare pred=key_compare()) : pred_(pred) {} template <typename L, typename R> inline typename std::enable_if<is_pair<L>::value && is_pair<R>::value, bool>::type operator()(L const& left, R const& right) const { return pred_(left.first,right.first); } template <typename L, typename R> inline typename std::enable_if<is_pair<L>::value && !is_pair<R>::value, bool>::type operator()(L const& left, R const& right) const { return pred_(left.first,right); } template <typename L, typename R> inline typename std::enable_if<!is_pair<L>::value && is_pair<R>::value, bool>::type operator()(L const& left, R const& right) const { return pred_(left,right.first); } template <typename L, typename R> inline typename std::enable_if<!is_pair<L>::value && !is_pair<R>::value, bool>::type operator()(L const& left, R const& right) const { return pred_(left,right); } inline key_compare key_comp( ) const {return pred_;} };
通常这会使两种方法更加接近。考虑到我希望std::vector<T>with lower_bound()方法应该比使用方法好得多,std::map<K, T>我认为调查还没有结束。
std::vector<T>
lower_bound()
std::map<K, T>
附录 :
重新思考多一点,我看到,为什么我觉得与谓词类的实现不舒服的运动:它是 这样 复杂!通过 不 进行std::enable_if更改,可以简化很多工作:这很好地将代码简化为更易于阅读的代码。关键是要获取密钥:
std::enable_if
template <typename Key> Key const& get_key(Key const& value) { return value; } template <typename Key, typename Value> Key const& get_key(std::pair<Key, Value> const& pair) { return pair.first; }
通过此实现从一个值或一对值中获取“键”,谓词对象可以定义一个非常简单的函数调用运算符:
template <typename L, typename R> bool operator()(L const& l, R const& r) { return this->pred_(get_key<key_type>(l), get_key<key_type>(r)); }
但是,这也有一个小技巧:key_type需要将期望的值传递给get_key()函数。如果没有此谓词,则key_type在本身std::pair<F, S>就是对象的情况下,谓词将无法工作。
key_type
get_key()
std::pair<F, S>