我需要在内存中连续存储已排序的元素,所以我想到了std :: vector和boost :: flat_set。我已经尝试了两种,我检查了它们的性能,并且尽管使用std :: vector后插入更快一点,使用boost :: flat_set可以更快地进行前插入。这是我的测试代码:
#include <iostream>
#include <vector>
#include <boost/container/flat_set.hpp>
#include <boost/chrono.hpp>
#include <windows.h>
// Just a basic movable object for my tests
class Component
{
public :
Component( void ) :
id( 0 ),
name( "default" ),
data( 100 )
{
}
Component( uint32_t id ) :
id( id ),
name( "default" ),
data( 100 )
{
}
Component( Component&& component ) throw() :
id( std::move( component.id ) ),
name( std::move( component.name ) ),
data( std::move( component.data ) )
{
}
Component& operator=( Component&& component ) throw()
{
id = std::move( component.id );
name = std::move( component.name );
data = std::move( component.data );
return ( *this );
}
uint32_t get_id( void ) const
{
return ( id );
}
private :
uint32_t id;
std::string name;
std::vector< uint32_t > data;
};
// This object can be sorted
inline bool operator<( const Component& component1, const Component& component2 )
{
return ( component1.get_id() < component2.get_id() );
}
#define COMP_NB 1000000
int main( void )
{
/*******************************/
/* Test vector insertion speed */
/*******************************/
std::vector< Component > vector;
vector.reserve( COMP_NB + 1 );
std::cout << "Push back components in the vector: ";
auto startTime = boost::chrono::steady_clock::now();
// Back insertion
for ( uint32_t i = 0; i < COMP_NB; ++i )
{
vector.push_back( Component( i + 1 ) );
}
auto thisTime = boost::chrono::steady_clock::now();
std::cout << boost::chrono::duration_cast< boost::chrono::milliseconds >( thisTime - startTime ).count() << "ms" << std::endl;
std::cout << "Insert one component at the beginning of the vector: ";
startTime = boost::chrono::steady_clock::now();
// Front insertion (all components are shifted)
vector.insert( vector.begin(), Component( 0 ) );
thisTime = boost::chrono::steady_clock::now();
std::cout << boost::chrono::duration_cast< boost::chrono::milliseconds >( thisTime - startTime ).count() << "ms" << std::endl;
/*********************************/
/* Test flat_set insertion speed */
/*********************************/
boost::container::flat_set< Component > flat_set;
flat_set.reserve( COMP_NB + 1 );
std::cout << "Push back components in the flat_set: ";
startTime = boost::chrono::steady_clock::now();
// Back insertion
for ( uint32_t i = 0; i < COMP_NB; ++i )
{
flat_set.insert( Component( i + 1 ) );
}
thisTime = boost::chrono::steady_clock::now();
std::cout << boost::chrono::duration_cast< boost::chrono::milliseconds >( thisTime - startTime ).count() << "ms" << std::endl;
std::cout << "Insert one component at the beginning of the flat_set: ";
startTime = boost::chrono::steady_clock::now();
// Front insertion (all components are shifted)
flat_set.insert( Component( 0 ) );
thisTime = boost::chrono::steady_clock::now();
std::cout << boost::chrono::duration_cast< boost::chrono::milliseconds >( thisTime - startTime ).count() << "ms" << std::endl;
system( "PAUSE" );
return ( 0 );
}
输出:
推回向量中的组件:852ms
在向量的开头插入一个组件:59ms
推回flat_set中的组件:912ms
在flat_set的开头插入一个组件:16ms
使用flat_set,前插入速度提高了3.6倍!所以我跑了另一个测试,因为我想看看我的移动功能是否被使用,我发现了一些奇怪的东西。这是新代码:
#include <iostream>
#include <vector>
#include <boost/container/flat_set.hpp>
#include <boost/chrono.hpp>
#include <windows.h>
// Just a basic movable object for my tests
class Component
{
public :
Component( void ) :
id( 0 ),
name( "default" ),
data( 100 )
{
std::cout << "Default constructor" << std::endl;
}
Component( uint32_t id ) :
id( id ),
name( "default" ),
data( 100 )
{
std::cout << "Custom constructor" << std::endl;
}
Component( Component&& component ) throw() :
id( std::move( component.id ) ),
name( std::move( component.name ) ),
data( std::move( component.data ) )
{
std::cout << "Move constructor" << std::endl;
}
Component& operator=( Component&& component ) throw()
{
std::cout << "Move assignment operator" << std::endl;
id = std::move( component.id );
name = std::move( component.name );
data = std::move( component.data );
return ( *this );
}
uint32_t get_id( void ) const
{
return ( id );
}
private :
uint32_t id;
std::string name;
std::vector< uint32_t > data;
};
// This object can be sorted
inline bool operator<( const Component& component1, const Component& component2 )
{
return ( component1.get_id() < component2.get_id() );
}
#define COMP_NB 1
int main( void )
{
/*******************************/
/* Test vector insertion speed */
/*******************************/
std::vector< Component > vector;
vector.reserve( COMP_NB + 1 );
std::cout << "-- Push back one component in the vector: " << std::endl;
// Back insertion
for ( uint32_t i = 0; i < COMP_NB; ++i )
{
vector.push_back( Component( i + 1 ) );
}
std::cout << "-- Insert one component at the beginning of the vector: " << std::endl;
// Front insertion (the other component is shifted)
vector.insert( vector.begin(), Component( 0 ) );
std::cout << std::endl;
/*********************************/
/* Test flat_set insertion speed */
/*********************************/
boost::container::flat_set< Component > flat_set;
flat_set.reserve( COMP_NB + 1 );
std::cout << "-- Push back one component in the flat_set: " << std::endl;
// Back insertion
for ( uint32_t i = 0; i < COMP_NB; ++i )
{
flat_set.insert( Component( i + 1 ) );
}
std::cout << "-- Insert one component at the beginning of the flat_set: " << std::endl;
// Front insertion (the other component is shifted)
flat_set.insert( Component( 0 ) );
system( "PAUSE" );
return ( 0 );
}
新输出:
- 向后推回向量中的一个组件:
定制构造函数
移动构造函数
- 在向量的开头插入一个组件:
定制构造函数
移动构造函数
移动构造函数
移动赋值运算符
移动赋值运算符
- 推回flat_set中的一个组件:
定制构造函数
移动构造函数
- 在flat_set的开头插入一个组件:
定制构造函数
移动构造函数
移动赋值运算符
这里有些奇怪的事。 Flat_set行为似乎很正常:
- 在flat_set的开头插入一个组件:
自定义构造函数//好的,我问了一个新组件的创建 移动构造函数//确定,flat_set需要将前一个组件移动到新位置
移动赋值运算符//确定,需要将新组件移动到flat_set的前面
但是矢量呢?
- 在向量的开头插入一个组件:
自定义构造函数//好的,我问了一个新组件的创建 移动构造函数//好的,向量需要将前一个组件移动到新位置
移动构造函数//等等......什么? 移动赋值运算符// OK,新组件需要移动到向量的前面
移动赋值运算符// Wtf?
我尝试了更多组件,并且向量行为是相同的:它继续执行所有移动操作两次。为什么?可以避免吗?如果没有,我应该坚持flat_set(我有时需要移动我的数据)?
[编辑]:这是输出,其中10个组件插入后面,1个插入前面,并且正在构造或移动组件的id: http://pastebin.com/SzT5M8yP
[编辑2]:我使用boost :: container :: vector进行了相同的测试,输出(和速度!)与flag_set相同。这是向量的Microsoft实现的问题吗? O.o
[编辑3]:提交给Microsoft的问题:https://connect.microsoft.com/VisualStudio/feedback/details/801205/std-vector-do-extra-operations-when-shifting-elements
答案 0 :(得分:2)
它没有进行两次所有移动操作,如果你的向量中有多个元素,你会看到只有一些操作发生两次。
要在N个元素(具有足够容量)的向量的开头插入一个rvalue,典型的方法是:
new (_M_data+N) T(_M_data[N-1]);
_M_size += 1;
for (int i = N-1; i > 0; --i)
_M_data[i] = std::move(_M_data[i-1]);
_M_data[0] = T(std::move(arg));
这意味着您将看到一个移动构造,然后是(N-1)移动分配(在您的情况下,向量只有一个元素,因此您在步骤2中看不到任何内容),然后是移动构造和移动分配。 / p>
在第3步中构造临时因为emplace
使用相同的插入逻辑作为insert
,所以它实际上T(std::move(args)...)
具有零个或多个args,在那里只是T
类型的一个参数,它可以只执行_M_data[0] = std::move(arg);
并避免移动构造。
我不确定为什么你最后会看到一个额外的移动任务,GCC的标准库实现不会这样做,我不确定为什么需要它。您可以非常轻松地修改代码以打印正在移动的对象的构造/分配的标识,以查看哪些元素被移动两次,这可能会对它有更多的了解。
答案 1 :(得分:0)
3年后,这就是我在邮箱中收到的内容:
来自Microsoft Connect的问候!
此通知是为反馈项生成的:std :: vector do 转移您提交的元素时的额外操作 theMicrosoft Connect网站。
您好,
感谢您报告此错误。我通过彻底检修修复了它 矢量的正确性和性能,这个修复程序将可用 在VS&#34; 15&#34; RC。以前,插入/放置称为rotate()in 某些情况。现在我们更智能地对我们的行为进行排序, 避免需要旋转元素。
Stephan T. Lavavej Visual C ++库的首席软件工程师 stl@microsoft.com
您可能会收到一般信息&#34;反馈项目已更新&#34;通知为 好吧,如果微软做出任何其他改动。
感谢您使用Microsoft Connect!
此致
Microsoft Connect小组
请不要直接回复此邮件,因为它是从中生成的 不受监控的电子邮件帐户。如果您有与您相关的评论 反馈,请在评论部分输入(发表评论至 Microsoft)您的反馈项目导航到的反馈项目 上面的链接。
如果您在访问上面的反馈链接时遇到问题,请转到 到http://connect.microsoft.com/help/default.aspx页面进行报告 问题,在您的提交中,请务必粘贴一份副本 将上面的内容链接到报告中。
乌拉!它很快就会修好。 :D认为它有点晚了,但它仍然是一件好事。
答案 2 :(得分:-1)
我的猜测是std::vector
通过分配一个新的向量,将当前的奇异值复制到新的向量,然后插入新的值来增加向量的大小。尝试删除对reserve
的调用,以便std::vector
的内存管理为您服务。