Вступление
В данной статье рассмотрен тест производительности заполнения (push_back) контейнеров объектами, содержащими разные типы указателей на разные типы данных. Тест комплексный, сочетающий в себе типичные задачи создания указателя на объект, копирование объекта, заполнение контейнера, освобождение выделенной под объект памяти средствами умного указателя и стандартным оператором delete. Будут протестированы три контейнера стандартной библиотеки шаблонов – vector, list, deque, три вида указателей – std::shared_ptr, std::auto_ptr и простой указатель. В качестве испытуемых типов данных (на которые будут создаваться указатели) использованы long, std::string, char, произвольный класс.
Описание теста
Исходный код написан в среде MS Visual Studio 2010, и требует от компилятора поддержку лямбда функций.
Пусть некоторый класс содержит описание указателя T на тип данных Type. Конструктор будет создавать объект, и инициализировать переданным параметром. Конструктор копирования будет копировать значение, находящееся по адресу указанного объекта, а также его имя и требование выводить в консоль сообщения о жизни объекта.
template< class T, typename Type >
class test_type_and_smart_pointer
{
public:
test_type_and_smart_pointer( std::string const& val, bool msg, Type type_val )
: _msg( msg ), _name( val )
{
if( _msg )
std::cout << "created " << _name << std::endl;
x.reset( new Type );
*x = type_val;
}
~test_type_and_smart_pointer()
{
if( _msg )
std::cout << "deleted " << _name << std::endl;
}
test_type_and_smart_pointer(const test_type_and_smart_pointer & W)
{
if( W._msg )
std::cout << "copied " << W._name << std::endl;
this->x.reset( new Type );
*(this->x) = *(W.x);
this->_name = W._name;
this->_msg = W._msg;
}
private:
T x;
bool _msg;
std::string _name;
};
Параметры шаблона:
- class T – подразумеваемый класс умного указателя,
- typename Type – тип данных создаваемого объекта.
Члены класса test_type_and_smart_pointer:
- T x — непосредственно сам указатель на объект,
- bool _msg — флаг вывода информационных сообщений,
- std::string _name – имя созданного объекта.
Создадим подобный класс для случая, если указатель T на тип Type будет стандартным.
template< class T, typename Type >
class test_type_and_simple_pointer
{
public:
test_type_and_simple_pointer( std::string const& val, bool msg, Type type_val )
: _msg( msg ), _name( val )
{
if( _msg )
std::cout << "created " << _name << std::endl;
x = new Type;
*x = type_val;
}
~test_type_and_simple_pointer()
{
delete x;
if( _msg )
std::cout << "deleted " << _name << std::endl;
}
test_type_and_simple_pointer(const test_type_and_simple_pointer & W)
{
if( W._msg )
std::cout << "copied " << W._name << std::endl;
this->x = new Type( *(W.x) );
this->_name = W._name;
this->_msg = W._msg;
}
private:
T x;
bool _msg;
std::string _name;
};
Теперь нужны функции, создающие выше описанные объекты:
template< typename _Ptr, typename T, typename Container, bool msg >
void test_smart_ptr( bool big_data, T val )
{
using namespace std;
test_type_and_smart_pointer< _Ptr, T > x( "a001", msg, val );
test_type_and_smart_pointer< _Ptr, T > x2( x );
Container cnt;
cnt.push_back( x );
cnt.push_back( x2 );
if( big_data )
for( int i = 0; i <= 100; i++ )
{
test_type_and_smart_pointer< _Ptr, T > xx( "a002", msg, val );
test_type_and_smart_pointer< _Ptr, T > xx2( xx );
cnt.push_back( xx );
cnt.push_back( xx2 );
}
cnt.clear();
}
template< typename T, typename Container, bool msg >
void test_simple_ptr( bool big_data, T val )
{
using namespace std;
test_type_and_simple_pointer< T*, T> x( "b001", msg, val );
test_type_and_simple_pointer< T*, T> x2( x );
Container cnt;
cnt.push_back( x );
cnt.push_back( x2 );
if( big_data )
for( int i = 0; i <= 100; i++ )
{
test_type_and_simple_pointer< T*, T> xx( "b002", msg, val );
test_type_and_simple_pointer< T*, T> xx2( xx );
cnt.push_back( xx );
cnt.push_back( xx2 );
}
cnt.clear();
}
Например, эти функции будут работать в двух режимах: создание пары указателей на объекты и размещение их в контейнере, и циклическое создание указателей на объекты и заполнение контейнера.
Время, затраченное на исполнение функций test_smart_ptr и test_simple_ptr будет измерять _time_test:
double _time_test( std::function< void() > test_func, unsigned int times )
{
using namespace std;
double start = 0, end = 0;
start = GetTickCount();
for( unsigned int i = 0; i < times; i++ )
{
test_func();
}
end = GetTickCount() - start;
//cout << "Elapsed ms: " << end <<
// " for " << times << " times " << endl;
return end;
}
Необходимые заголовочные файлы:
#include "iostream"
#include "string"
#include "vector"
#include "list"
#include "deque "
#include <Windows.h>
#include "functional"
В качестве объекта произвольного типа использован простейший класс без динамических выделений памяти.
class some_obj
{
public:
some_obj()
{
i = 90000;
s = "1231231231231231231232";
d = 39482.27392;
}
~some_obj()
{
}
private:
int i;
std::string s;
double d;
};
Итак, как проводился тест. Описаны 72 лямбда функции, каждая из которых при завершении приводит кучу в исходное состояние.
int main()
{
using namespace std;
const long long_val = 900000000;
const char ch = 'a';
const string str = "abc";
///////////////////////////////////////////////////////////////////////////////////////////////////////
auto f_01 = [&]() -> void
{ test_smart_ptr< shared_ptr< long >, long, vector< test_type_and_smart_pointer< shared_ptr< long >, long > >, false >( false, long_val ); };
auto f_02 = [&]() -> void
{ test_smart_ptr< shared_ptr< char >, char, vector< test_type_and_smart_pointer< shared_ptr< char >, char > >, false >( false, ch ); };
auto f_03 = [&]() -> void
{ test_smart_ptr< shared_ptr< string >, string, vector< test_type_and_smart_pointer< shared_ptr< string >, string > >, false >( false, str ); };
auto f_04 = [&]() -> void
{
some_obj x;
test_smart_ptr< shared_ptr< some_obj >, some_obj, vector< test_type_and_smart_pointer< shared_ptr< some_obj >, some_obj > >, false >( false, x );
};
...
_time_test( f_01, 100000 );
_time_test( f_02, 100000 );
_time_test( f_03, 100000 );
_time_test( f_04, 100000 );
...
return 0;
}
Полный код функции main не приведен, ввиду громоздкости, но по первому блоку лямбда функций для указателя shared_ptr общая тенденция комбинаций параметров шаблонов видна.
Каждая функция f_nn выполнялась 15 раз и результат измерения усреднен (запуск был в релизном режиме не из под студии). За единицу принято самое малое затраченное время, т.е. остальные значения приведены в пропорции.
Результаты теста
Цветом обозначен наиболее быстрый тип данных по каждому типу указателя среди всех контейнеров:
Наиболее быстрые типы данных по каждому контейнеру среди всех типов указателей:
Наиболее быстрый контейнер при заполнении по всем типам данных и по всем указателям:
Что полезное можно извлечь?
Во-первых, по результатам теста видно, какой контейнер заполняется быстрее.
Во-вторых, понятно как в пропорциональном соотношении по скорости работают указатели на типичных задачах.