Меню сайта
Наш опрос
Оцените мой сайт
Всего ответов: 5
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Главная » 2014 » Июль » 22 » Анализ и оптимизация кода на C++ для Linux
15:22
Анализ и оптимизация кода на C++ для Linux
Какие проблемы могут нас подстерегать при разработке программ? Неэффективные алгоритмы, утечки памяти, работа с невалидными указателями и не инициализированными переменными. Даже несмотря на, казалось бы, тщательное написание кода, мы порой делаем ошибки. Человеку свойственно ошибаться, поэтому эта статья посвящена контролю машин над человеком -- машинной проверки кода и исполняемых файлов. Я поделю весь процесс на три части:Статический анализ исходных текстов;Проверка утечек памяти;Нахождение участков кода, требующих неприлично много машинного времени.В этой статье, говоря о компиляторе, я буду подразумевать g++, входящий в состав GCC (GNU Compiler Collection).Статический анализ исходных текстовЭтот раздел опирается, во многом, на этот пост на хабре.g++ Наряду со статическими анализаторами, компилятор g++ может выдать очень много полезной информации. Для того, чтобы добиться от компилятора максимум возмущений, следует добавит несколько опций.-Wall - включает почти все стандартные предупреждения. Эту опцию нужно ставить всегда и везде, это должно стать Вашим правилом.-Wextra - сообщит об ошибках в условных операторах, пустые if'ы и сравнение signed с unsigned.-pedantic - по приказу этой опции компилятор начнёт следовать стандарту ISO C++. Например, запретит тип long long.-Weffc++ -  эта опция напомнит Вам о некоторых правилах Скотта Майерса, которые Вы все, надеюсь, читали. В частности, это следующие правила из книги "Эффективное использование C++. 50 рекомендаций по улучшению ваших программ и проектов":Правило 11. "Для классов с динамическим выделением памяти объявляйте копирующий конструктор и оператор присваивания".Правило 12. "Предпочитайте инициализацию присваиванию в конструкторах".Правило 14. "Убедитесь, что базовые классы имеют виртуальные деструкторы".Правило 15. .Правило 23. "Никогда не пытайтесь вернуть ссылку, когда вы должны вернуть объект".И несколько правил из книги "Наиболее эффективное использование C++. 35 новых рекомендаций по улучшению ваших программ и проектов":Правило 6. "Различайте префиксную и постфиксную формы операторов инкремента и декремента". Правило 7. "Никогда не перегружайте операторы &&, || и ,".-Woverloaded-virtual - сообщит о перегрузке виртуальных функций.-Wctor-dtor-privacy - возмутится, если найдёт у Вас в коде класс с закрытыми конструкторами и деструктором, который нигде не используется.-Wnon-virtual-dtor - этой опции не нравятся не виртуальные деструкторы.-Wold-style-cast - поможет избавится от приведений типов в стиле языка C.-Wconversion -Wsign-conversion - заставят компилятор сказать пару ласковых о неверных приведениях типов, при которых Вы можете лишиться  части своих значений.-Wunreachable-code - укажет на участки кода, которые никогда не будут выполнены. Эта опция может выдать очень много ворнингов даже в стандартной библиотеке, полезно её включать только при проверке.Давайте проверим всё вышесказанное на практике. Для этой цели я написал следующий код полный ошибок#include <iostream>class CBicycle{public: CBicycle(unsigned short max_speed) { m_max_speed = max_speed; } unsigned short GetMaxSpeed() const { return m_max_speed; }private: short m_max_speed;};class CBicyclist{public: CBicyclist(const CBicycle & bicycle) { mp_bicycle = new CBicycle(bicycle); m_speed = 0; } unsigned short GetSpeed() const { return m_speed; } bool Move(short speed) { bool result = true; if(speed I'm move with speed of " << m_speed << " km/h\n"; } else { result = false; std::cout << "Sorry, this bicycle can't move with speed of " << speed << " km/h\n"; } return result; }private: CBicycle * mp_bicycle; unsigned short m_speed;};int TestMaxSpeed(CBicyclist & bicyclist){ int speed = bicyclist.GetSpeed(); int max_speed = speed; bool loop = true; do { if(bicyclist.Move(max_speed)) { ++max_speed; } else { loop = false; --max_speed; bicyclist.Move(speed); } } while(loop); return max_speed;}int main(){ CBicycle * bicycle = new CBicycle(75); CBicyclist * bicyclist = new CBicyclist(*bicycle); bicyclist->Move(600); std::cout << "Begin max speed test\n"; int max_speed = TestMaxSpeed(*bicyclist); std::cout << "End max speed test. Max speed = " << max_speed << std::endl; return 0;}У нас есть некое подобие классов, класс велосипеда и класс велосипедиста. Велосипед принимает в конструкторе максимальную скорость, а велосипедист - велосипед. Велосипедист может разогнаться на столько быстро, на сколько позволит велосипед или тип unsigned short, с которым у меня в коде связано много ошибок. Класс велосипедиста "забывает" удалить свой велосипед, а функция main "забывает" не только про велосипед, но и про велосипедиста. Задача функции TestMaxSpeed проверить, какая же скорость у велосипеда максимальная, используя для этой цели только велосипедиста. Но и у этой функции не всё в порядке с типами.Итак, скомпилируем код с вышеуказанными опциями. $ g++ -Wall -Wextra -pedantic -Weffc++ -Woverloaded-virtual -Wctor-dtor-privacy -Wnon-virtual-dtor -Wold-style-cast -Wconversion -Wsign-conversion -Wunreachable-code -g -O0 -c main.cppmain.cpp: In constructor ‘CBicycle::CBicycle(short unsigned int)’:main.cpp:6: warning: ‘CBicycle::m_max_speed’ should be initialized in the member initialization listmain.cpp:8: warning: conversion to ‘short int’ from ‘short unsigned int’ may change the sign of the resultmain.cpp: In member function ‘short unsigned int CBicycle::GetMaxSpeed() const’:main.cpp:13: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the resultmain.cpp: In constructor ‘CBicyclist::CBicyclist(const CBicycle&)’:main.cpp:23: warning: ‘CBicyclist::mp_bicycle’ should be initialized in the member initialization listmain.cpp:23: warning: ‘CBicyclist::m_speed’ should be initialized in the member initialization listmain.cpp: In member function ‘bool CBicyclist::Move(short int)’:main.cpp:39: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the resultmain.cpp: In function ‘int TestMaxSpeed(CBicyclist&)’:main.cpp:63: warning: conversion to ‘short int’ from ‘int’ may alter its valuemain.cpp:71: warning: conversion to ‘short int’ from ‘int’ may alter its valuemain.cpp: In constructor ‘CBicyclist::CBicyclist(const CBicycle&)’:main.cpp:25: warning: will never be executedg++ -Wall -Wextra -pedantic -Weffc++ -Woverloaded-virtual -Wctor-dtor-privacy -Wnon-virtual-dtor -Wold-style-cast -Wconversion -Wsign-conversion -Wunreachable-code -g -O0 *.o -o testТеперь давайте проанализируем вывод компилятора.main.cpp: In constructor ‘CBicycle::CBicycle(short unsigned int)’:main.cpp:6: warning: ‘CBicycle::m_max_speed’ should be initialized in the member initialization listНам сообщают, что в конструкторе класса CBicycle мы должны инициализировать член m_speed в списке инициализации, а не в теле конструктора.main.cpp:8: warning: conversion to ‘short int’ from ‘short unsigned int’ may change the sign of the resultКонструктор CBicycle принимает значение unsigned short и присваивает его, почему-то, переменной типа short. (Это мы забыли написать unsigned в типе переменной-члена m_max_speed).main.cpp: In member function ‘short unsigned int CBicycle::GetMaxSpeed() const’:main.cpp:13: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the resultТеперь мы ещё и возвращаем short от туда, от куда должны вернуть unsigned short.main.cpp:23: warning: ‘CBicyclist::mp_bicycle’ should be initialized in the member initialization listmain.cpp:23: warning: ‘CBicyclist::m_speed’ should be initialized in the member initialization listКласс CBicyclist повторил "подвиг" класса CBicycle и инициализировал все свои переменные в теле, а не в списке инициализации.main.cpp: In member function ‘bool CBicyclist::Move(short int)’:main.cpp:39: warning: conversion to ‘short unsigned int’ from ‘short int’ may change the sign of the resultОпять забыли про unsigned, какие же мы невнимательные.main.cpp: In function ‘int TestMaxSpeed(CBicyclist&)’:main.cpp:63: warning: conversion to ‘short int’ from ‘int’ may alter its valuemain.cpp:71: warning: conversion to ‘short int’ from ‘int’ may alter its valueА это уже претенденты на потерю данных, конверсия из int в short.main.cpp: In constructor ‘CBicyclist::CBicyclist(const CBicycle&)’:main.cpp:25: warning: will never be executedЭтот ворнинг выдаёт опция -Wunreachable-code, его нужно проанализировать, и если Вы уверены, что всё в порядке -- можно проигнорировать.cppcheck Существуют специальные программы для анализа исходных текстов на C++, которые позволяют выявить потенциальные ошибки ещё до сборки (или использования) программ. Одна из таких программ -- cppcheck. В man руководстве к этой программе говорится, что она предназначена для выявления тех ошибок, которые не находит компилятор. Это такие ошибки как некоторые утечки памяти, выход за границу массива, исключения в деструкторах, разыменование нулевых и освобождённых указателей, виртуальность деструктора базовых классов и другое.Работать с этой программой очень просто. Для демонстрации возьмём следующий небольшой пример.int main(){ int * x = new int[10]; x = 0; return 0;}После запуска cppcheck в этом малюсеньком коде обнаруживается сразу две ошибки.$ cppcheck x' is assigned a value that is never used[./main.cpp:7]: (error) Memory leak: x Checking usage of global functions..Во-первых, мы объявили переменную x и никогда её не используем, а во-вторых, у нас зафиксирована утечка памяти.Проверка утечек памятиПрактика показывает, что не всегда можно найти утечки памяти статическими анализаторами. Первый приведённый в этой статье листинг -- тому подтверждение. Если запустить cppcheck для этой программы, то мы ничего не найдём. Но отчаиваться ещё рано, нам может помочь утилита valgrind, предназначенная специально для этого.  Эта утилита находит утечки памяти не статически, а при работе программы. Давайте познокомимся с выводом этой программы$ valgrind d, by Julian Seward et t have a dynamic symbol table--31174-- Reading suppressions file: /usr/lib/valgrind/debian-libc6-dbg.supp--31174-- Reading suppressions file: /usr/lib/valgrind/default.supp--31174-- REDIR: 0x40162d0 (strlen) redirected to 0x380408a7 (vgPlain_amd64_linux_REDIR_FOR_strlen)--31174-- Reading syms from /usr/lib/valgrind/vgpreload_core-amd64-linux.so (0x4a20000)--31174-- Reading syms from /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so t have a symbol table--31174-- Reading syms from /lib/libm-2.11.2.so (0x513d000)--31174-- Considering /lib/libm-2.11.2.so ..--31174-- .. CRC mismatch (computed b0a7ab6b wanted 907fac55)--31174-- Considering /usr/lib/debug/lib/libm-2.11.2.so ..--31174-- .. CRC is valid--31174-- Reading syms from /lib/libgcc_s.so.1 (0x53bf000)--31174-- Considering /lib/libgcc_s.so.1 ..--31174-- .. CRC mismatch (computed 07151771 wanted 3f9779e8)--31174-- object doesn't have a symbol table--31174-- Reading syms from /lib/libc-2.11.2.so (0x55d5000)--31174-- Considering /lib/libc-2.11.2.so ..--31174-- .. CRC mismatch (computed 21e032ea wanted d5c67601)--31174-- Considering /usr/lib/debug/lib/libc-2.11.2.so ..--31174-- .. CRC is valid--31174-- REDIR: 0x5652600 (__GI_strrchr) redirected to 0x4c25280 (__GI_strrchr)--31174-- REDIR: 0x5650b40 (__GI_strlen) redirected to 0x4c25810 (__GI_strlen)--31174-- REDIR: 0x4ef46a0 (operator new(unsigned long)) redirected to 0x4c24d78 (operator new(unsigned long))--31174-- REDIR: 0x5650b10 (strlen) redirected to 0x4a205ac allocatedТри раза память была выделена, но ни разу не освобождалась. Чуть далее в выводе программы valgrind мы можем найти информацию немного (main.cpp:80)Вот тут-то valgrind и сдаёт нас с потрохами, выдавая все наши грехи.Оптимизация кодаТеперь пришло время задуматься о производительности. Пусть у нас есть такой код#include <iostream>#include <fstream>#include <string>#include <vector>#include <algorithm>#include <cstdlib>#include <sys/time.h>template<typename Type>std::vector<Type> & Sort(std::vector<Type> & array){ size_t arr_size = array.size(); for(size_t i = 0; i < arr_size; ++i) { bool swaped = false; for(size_t j = 1; j < arr_size; ++j) { if(array[j - 1] > array[j]) { std::swap(array[j - 1], array[j]); swaped = true; } } if(!swaped) break; } return array;}template<typename Type>class CWriter{public: CWriter(std::ostream & stream) : mr_stream(stream) { } void operator () (const Type & value) { mr_stream << value << std::endl; }private: std::ostream & mr_stream;};template<typename Type>bool WriteArrayToFile(const std::string & filename, const std::vector<Type> & array){ bool result = false; std::ofstream out(filename.c_str()); if(out.is_open()) { std::for_each(array.begin(), array.end(), CWriter<Type>(out)); result = true; out.close(); } return result;}int main(int argc, char ** argv){ if(argc < 3) { std::cerr << "To few arguments\n"; return 1; } size_t array_size = static_cast<size_t>(::atoi(argv[1])); std::string filename = argv[2]; ::srand(static_cast<unsigned int>(::time(0))); std::vector<int> array; array.reserve(array_size); for(size_t i = 0; i < array_size; ++i) { array.push_back(::rand()); } if(! ::WriteArrayToFile(filename, ::Sort(array))) { std::cerr << "Error writing the file with name \"" << filename << "\"\n"; return 2; } return 0;}Здесь генерируется массив из n элементов, сортируется и записывается в файл. Если мы его запустим, то увидим картину, не очень-то удовлетваряющую нас -- большие затраты времени. В этом можно убедиться, запустив такую команду$ time ./test 100000 test.txtreal 0m24.913suser 0m24.566ssys 0m0.344s25 секунд -- это, явно, слишком много. Естественным желанием будет оптимизировать работу программы так, чтобы предельно сократить время её работы. Из данного примера очевидно, что пузырьковая сортировка и является причиной нашего недовольства, но в больших проектах причина может быть не столь очевидной. Давайте сделаем вид, что мы не знаем причину столь затяжного выполнения и попробуем найти её методом профилирования программы. А поможет нам в этом утилита с именем gprof. Подробно о работе с этой утилитой можно прочитать на man-странице проекта opennet.Для того, чтобы можно было работать с профилировщиком gprof, нужно добавить опцию -pg к команде сборки проекта, а затем собрать проект с отладочными символами. После сборки нужно запустить программу с тем же параметрами, с которыми мы получили неудовлетворительный результат, что приведёт к созданию файла gmon.conf в последнем текущем каталоге программы, а у нас этот каталог не менялся. Запустить программу gprof можно следующим образом$ gprof ./test > gprof.logНа выходе программа gprof выдаёт очень много информации, поэтому её лучше перенаправить в файл. Теперь нужно открыть этот файл и проанализировать его. Я советую открывать этот файл в текстовом редакторе, поддерживающем подсветку синтаксиса и выбрать режим подсветки C++, так как профиль и граф вызовов содержат много текста на C++. Кроме того, лучше отключить динамический перенос строк. Я приведу только часть простого профиля файла, который у меня получился.Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls s/call s/call name 55.50 98.49 98.49 1 98.49 154.05 std::vector<int, std::allocator<int> >& Sort<int>(std::vector<int, std::allocator<int> >&) 24.22 141.47 42.99 7769644628 0.00 0.00 std::vector<int, std::allocator<int> >::operator[](unsigned long) 10.77 160.58 19.11 73 0.26 0.26 std::vector<int, std::allocator<int> >::size() const 6.94 172.89 12.31 2507056584 0.00 0.00 void std::swap<int>(int&, int&)Из приведённых выше строк простого профиля видно, что 55,5% времени программа тратит на сортировку и ещё 24,22% на обращение к элементам вектора. Скорее всего, нам удастся решить обе проблемы, подставив более эффективный алгоритм сортировки. Давайте проверим, заменим сортировку пузырьком сортировкой слияниями.#include <iostream>#include <fstream>#include <string>#include <vector>#include <algorithm>#include <cstdlib>#include <sys/time.h>template<typename Type>std::vector<Type> & MergeSort(std::vector<Type> & array){ struct CMergeSort { void Sort(std::vector<Type> & array) { size_t array_size = array.size(); if(array_size To few arguments\n"; return 1; } size_t array_size = static_cast<size_t>(::atoi(argv[1])); std::string filename = argv[2]; ::srand(static_cast<unsigned int>(::time(0))); std::vector<int> array; array.reserve(array_size); for(size_t i = 0; i < array_size; ++i) { array.push_back(::rand()); } if(! ::WriteArrayToFile(filename, ::MergeSort(array))) { std::cerr << "Error writing the file with name \"" << filename << "\"\n"; return 2; } return 0;}Если мы откомпилируем этот код и запустим программу, то получим следующее$ time ./test 100000 test.txtreal 0m0.437suser 0m0.076ssys 0m0.360sОчевидно, что такие результаты нас устраивают.---------------Источники:Статический анализ кода C++Профилятор gprof
Просмотров: 478 | Добавил: admin | Рейтинг: 0.0/0
Всего комментариев: 0
avatar
Форма входа
Календарь
«  Июль 2014  »
ПнВтСрЧтПтСбВс
 123456
78910111213
14151617181920
21222324252627
28293031
Архив записей