Вступление
В данной статье рассмотрен тест производительности заполнения (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 раз и результат измерения усреднен (запуск был в релизном режиме не из под студии). За единицу принято самое малое затраченное время, т.е. остальные значения приведены в пропорции.
Результаты теста
Цветом обозначен наиболее быстрый тип данных по каждому типу указателя среди всех контейнеров:

Наиболее быстрые типы данных по каждому контейнеру среди всех типов указателей:

Наиболее быстрый контейнер при заполнении по всем типам данных и по всем указателям:

Что полезное можно извлечь?
Во-первых, по результатам теста видно, какой контейнер заполняется быстрее.
Во-вторых, понятно как в пропорциональном соотношении по скорости работают указатели на типичных задачах.
