【发布时间】:2013-09-21 19:54:58
【问题描述】:
我需要将排序后的元素连续存储在内存中,所以我想到了 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 需要将前一个组件移动到一个新的位置
移动赋值运算符 //OK,新组件需要移动到flat_set的前面
但是向量呢?
-- 在向量开头插入一个分量:
自定义构造函数//好吧,我问的是新组件的创建
移动构造函数 //好的,向量需要将前一个组件移动到一个新的位置
移动构造函数//等等...什么?
移动赋值运算符 //OK,新组件需要移动到向量的前面
移动赋值运算符 //Wtf?
我尝试了更多组件,但向量行为是相同的:它会继续执行所有移动操作两次。为什么?可以避免吗?如果没有,我应该坚持使用 flat_set(我有时需要转移我的数据)吗?
[Edit] :这是输出,其中 10 个组件被插入到后面,1 个组件被插入到前面,并且组件的 id 正在构建或移动: http://pastebin.com/SzT5M8yP
[Edit 2]:我用 boost::container::vector 做了同样的测试,输出(和速度!)与 flag_set 相同。微软实现vector有问题吗?哎哟
[编辑 3]:提交给 Microsoft 的问题:https://connect.microsoft.com/VisualStudio/feedback/details/801205/std-vector-do-extra-operations-when-shifting-elements
【问题讨论】:
-
这可能是在向量中使用子函数的情况......这是一个优化的构建吗? (可以省略复制/移动构造函数的调用)
-
哦,对不起,我忘了提这个。我使用的是 Visual Studio 2013,我的测试是使用默认的发布设置进行的,所以是的,优化已启用。
-
Boost 标头路径应该是
boost而不是Boost。两者都适用于 Windows,因为文件系统不区分大小写,但使用Boost只会产生不必要的可移植性问题。 -
确实如此,但在我实际工作的项目中,我使用的是 DX11 和 win32 API,因此这里的可移植性并不是一个真正的问题。 ;)
-
除非您现在已将代码粘贴到 SO 上,而且人们(例如我)必须修复在他们自己的平台上运行代码的路径。
标签: c++ boost c++11 vector move