19 авг. 2009 г.

Советы по улучшению производительности программ для Symbian OS

Ниже приводится мой перевод одного из буклетов от издательства Symbian Press: "Советов по улучшению производительности программ", или "Performance Tips".


Содержание.

Что такое "производительность"?
Почему производительность столь важна?
Убийцы производительности

Код, повторяющийся в циклах
Неэффективное использование динамической памяти
Ограниченное понимание возможностей библиотеки
Нежелательное приведение типов
Неэффективное использование файлов
Неэффективное использование базы данных
Плохое использование шаблонов проектирования
Обычный код, и код, создаваемый "на будущее"

Разработка и тестирование на эмуляторе
Вы, ваш компилятор и ваш код

Не идите против компилятора
Выучите немного ассемблера

Маленькие советы

Избегайте вызова функций внутри определения условия циклов
Используйте ссылки или указатели там, где это нужно
Не раскручивайте циклы
Избегайте длинных блоков из if и else
Используйте соответствующим образом квалификатор const
Быстрый анализ кода при помощи User::FastCounter()

Инструменты: пошаговый анализатор

Компиляция ROM-файла
Запуск анализатора
Запуск изучаемого кода
Остановка анализатора
Получение данных анализатора
Анализ данных
Копирование файла с данными
Создание графа активности
Выберите активную область и потоки
Создание листинга по всем функциям

Ресурсы для разработчиков






Что такое "производительность"?

В сегодняшнем мире все более растущих скоростей связи и "крутых фичек", смартфоны все более и более нуждаются в производительных программах. Для создания таких программ их код должен работать настолько эффективно, насколько это вообще возможно. И проблема тут заключается не в подсчете циклов процессора, а в ряде простых вещей, на которые стоит обратить внимание при создании вашего приложения.

Под "производительностью" понимается ряд характеристик, которые можно измерить во время работы устройства: время загрузки, размер ROM-памяти, загруженность ROM-памяти, вывод на экран изображений, время жизни батареи, и т.д. Предназначение устройства и наличие в нем ряда особенностей могут определить желаемые значения для представленных характеристик. Для достижения этих желаемых значений, программы смартфона должны быть спроектированы и реализованы подобающим образом.

Почему производительность столь важна?

Обычным способом повышения производительности программ является либо увеличение скорости работы центрального процессора, либо увеличение количества RAM-памяти, отдаваемой под нужды кеширования. На практике ни один из этих методов не доступен для производителей мобильных устройств, так как устройства должны получаться дешевыми, и способными долго работать от одной батареи.

Убийцы производительности

Большинство проблем, связанных с производительностью программ на смартфонах, принадлежит к той или иной категории других, более маленьких проблем. Рассмотрением этих категорий мы и займемся далее.

Код, повторяющийся в циклах

Излишние вычисления очень часто идут рука об руку с конструированием сложных типов. Обратите внимание на следующий пример:

// Метод, выполняющий некую простую операцию
ExampleClass::SimpleOperation( SimpleType a, SimpleType b )
{
// Создание ненужного сложного типа (см. текст)
ComplexType c = b.MakeComplex();

// Еще какой-нибудь код
}

// Выполнить сложные вычисления
ExampleClass::DoSomeComplexComputation( ... )
{
SimpleType a, b;

while ( moreToDo )
{
// Какой-нибудь код

SimpleOperation( a, b );

// Еще какой-нибудь код,
// который не изменяет переменную b
}
}

В вышеприведенном примере метод SimpleOperation(), выполняющий некую простую операцию, вызывается в цикле. При каждом выполнении тела цикла, вызывается метод SimpleOperation(), в котором от аргумента класса SimpleType создается один и тот же сложный объект класса ComplexType. При этом сложный тип никак не модифицируется в последующем коде. Такое цикличное создание ненужных объектов класса ComplexType понапрасну растрачивает ресурсы, и может привести к существенному снижению производительности кода. Если бы объект класса ComplexType можно было бы передать методу SimpleOperation() в качестве аргумента, его повторного создания можно было бы просто избежать:
 
ExampleClass::SimpleOperation( SimpleType a, ComplexType& b )
{
// Код простого метода
}

CExampleClass::DoSomeComplexComputation( ... )
{
SimpleType a;
ComplexType b; // Создать объект b в виде сложного типа

while ( moreToDo )
{
// Передать в функцию объект b в виде сложного типа ComplexType
SimpleOperation( a, b );

// Еще выполнить код
}
}

Подводя итоги, можно сказать: будьте внимательны к тому, чтобы в интенсивно используемых циклах не было повторяющихся вычислений или однообразной обработки данных.

Неэффективное использование динамической памяти

Очень часто во встраиваемых системах динамическая память используется вместо стека. Без должного внимания, такая практика может привести к неумерным обращениям к "куче", так как стек обычно используется под временные переменные.

void LoopWithHeap( void )
{
while ( moreToDo )
{
CData* temp = new CData; // Создаем временную переменную на "куче"
GetData( temp ); // Инициализируем
ProcessData( temp ); // Используем
delete temp; // Удаляем
}
}

Всегда, где это возможно, временные переменные должны использоваться заново:

void LoopWithHeap( void )
{
CData* temp = new CData; // Создаем переменную на "куче"

while ( moreToDo )
{
GetData( temp ); // Инициализируем
ProcessData( temp ); // Используем
temp->Reset(); // Обнуляем
}

delete temp; // Удаляем в конце цикла
}

Примером неэффективного использования динамической памяти так же является использование сегментированных структур данных с гранулярностью, размеры которой намного превышают реальное количество обрабатываемых данных.

Другим возможным примером неразумного использования "кучи" является излишняя потребность кода в перераспределении динамической памяти. Плохо продуманный алгоритм программы может привести к постоянному выделению, освобождению и копированию ячеек динамической области памяти.

Ограниченное понимание возможностей библиотеки

Документация по API редко когда содержит подробные сведения о реализации той или иной компоненты. Создание кода на основе некорректно или плохо понятого API может привести к таким проблемам как двойная или нежелательная обработка и модификация данных.

Представьте себе класс, предоставляющий доступ к массиву и реализующий проверку границ его индексов в методе SetElement():

void ArrayClass::SetSize( int aSize )
{
iMaxLength = aSize;
}

void ArrayClass::SetElement( int aPos, unsigned char aChar )
{
if ( aPos >= 0 && aPos < iMaxLength )
{
iRawArray[ aPos ] = aChar;
}
}

А теперь представьте себе программу, которой нужно добавить некоторое число элементов в массив при помощи представленного выше класса:

void ExampleClass::FillArray()
{
// Какой-то код
myArray.SetSize( bytesToProcess );

for ( currentPos = 0; currentPos < bytesToProcess; currentPos++ )
{
myArray.SetElement( currentPos, aByte );
}
}

Неэффективность приведенного кода заключается здесь в ненужности проверки границ индексов массива внутри метода SetElement(), так как функция FillArray() устанавливает верхнюю границу для элементов массива внутри себя.

Подобные проблемы могут иметь множество источников. Возможно, разработчик класса ExampleClass не знал, что класс ArrayClass проверяет границы массива. Возможно, разработчик не знал об особенностях API класса ArrayClass. Возможно, что создатель класса ArrayClass не определил нужный нашему программисту API, так как не предполагал, что класс массива будет использоваться подобным образом.

Нежелательное приведение типов

При наличии плохого дизайна структур данных, время работы процессора бесполезным образом тратится на приведение типов. Приведение типов обычно носит статический характер, и выполняется для передачи данных внешним API.

Обратите внимание на пример приведения типа данных:

TInt intDrive;
TChar ch = ((*drives) [ i ])[ 0 ];
RFs::CharToDrive( ch, intDrive );
TDriveUnit curDrive( intDrive );

В коде необходимо использовать тип TDriveUnit, однако имя диска было сохранено в символьном виде, поэтому нам приходится трижды обрабатывать данные, прежде чем они станут пригодными для нашей программы. А теперь представьте, что данный код расположен в глубине интенсивно используемого цикла, и что большая часть работы цикла будет посвящена выполнению этих трех операций. По этому возможно более подходящим решением стало бы хранение отдельной переменной типа TDriveUnit, которую можно было бы прямо использовать в дальнейшем внутри цикла. Иногда проблема приведения типов может привести к размещению множества временных объектов в стеке программы, исключительно лишь в целях изменения интерфейса к конкретному объекту.

Обратите внимание на нижеприведенный пример, в котором мы можем наблюдать создание временных объектов только лишь ради вызова различных методов:

iDllEntry.iName.Des().Zero();
iDllEntry.iName.Des().Append( aPath.Drive() );
iDllEntry.iName.Des().Append( KSysBin );
iDllEntry.iName.Des().Append( *resourceName );
iDllEntry.iName.Des().Append( KDllExtension );

Пример продемонстрировал нам две проблемы. Главная проблема заключается в создании методом Des() временного объекта. В течении выполнения вышеизложенного года, создаются 5 временных объектов, создание которых можно было бы избежать при помощи одной локальной переменной:

TPtr des = iDllEntry.iName.Des();
des.Zero();
des.Append( aPath.Drive() );
des.Append( KSysBin );
des.Append( *resourceName );
des.Append( KDllExtension );

Менее заметная проблема заключается в используемых параметрах компилятора. Создатели метода Des() определили его как встраиваемый метод, однако если компилятору были даны указания на оптимизацию объемов производимого кода, то при частом вызове методов Des(), компилятор не станет встраивать эти методы, а займется оптимизацией их вызовов.

Неэффективное использование файлов

В данную категорию проблем попадает не только неэффективное использование файлов, но и всех других источников данных, чья скорость доступа значительно ниже, чем у оперативной памяти. Помимо файлов, такими источниками данных могут являться электронные устройства, а так же сетевые коммуникации.

Неэффективность использования файлов может проявиться при использовании файловой системы в качестве базы данных, в которой файлам и директориям отводится роль структуры этой базы.

Другой пример неэффективного использования файловой системы заключается в синхронном, поблочном чтении и обработки данных из файла (или любого другого медленного источника информации). Если вместо поточной обработки информации используется блочная, код, обрабатывающий данные, вынужден ждать момента когда очередной блок данных будет считан и передан ему.

В следующем примере мы можем обнаружить другую проблему использования файлов, а именно - чтение данных в виде нескольких маленьких блоков:

EXPORT_C CColorList* ColorUtils::CreateSystemColorListL( RFs& aFs )
{

CDirectFileStore* store;
store = CDirectFileStore::OpenL( aFs, KGulColorSchemeFileName, EFileRead | EFileShareReadersOnly ));

RStoreReadStream stream;
stream.OpenL( *store, store->Root() );

CColorList* colorList = CColorList::NewLC();
stream >> *colorList;
return colorList;
}

Проблема здесь заключается в реализации перегруженного оператора ">>", чья реализация осуществляется методом InternalizeL(). Ниже представлен фрагмент этой функции:

aStream >> card;
const TInt count( card );
TRgb rgb;

for ( TInt ii=0; ii < count; ii++ )
{
aStream >> rgb;
iEikColors->AppendL( rgb );
}

Здесь мы можем убедиться, что для каждого встроенного класса вызывается оператор ">>". Если мы проследим все вызовы этих функций, то обнаружим, что они создают данные при помощи 32-битных блоков, и каждый такой блок отдельно считывается из файла. Даже если для нас у файлового сервера есть заранее считанные данные, работа функций все равно приведет к снижению производительности всей программы. Вместо вышеприведенного примера, попробуйте посмотреть на следующий пример кода:

aStream >> card;
const TInt count( card );
aStream->ReadL( iEikColors, count * sizeof( TRgb ) );

Такой код будет работать значительно быстрее, и для него нужно всего лишь убедиться, что внутренний формат структуры TRgb не был изменен.

Неэффективное использование базы данных

Проблемы неэффективного использования систем баз данных в Symbian OS очень близки по смыслу проблемам неэффективного использования файловой системы. Обе системы баз данных, имеющиеся в Symbian OS, весьма широко используют в своей работе файловую систему. Поэтому неправильное использование API баз данных может привести к тем же проблемам, что и в случаях неэффективного использования файловой системы.

Первое, что нужно отметить, - это использование Compact() API. В целях улучшения качества работы базы данных, их подсистемы не производят изменения над данными базы "на лету". Изменения базы данных происходит лишь по прошествии определенного отрезка времени или по накоплению определенного количества изменений в структурах базы. Во время изменения, новые структуры добавляются в конец базы данных, и помечаются различными маркерам. Постоянные изменения содержимого базы данных приводят к росту ее объема. Именно поэтому и существует Compact() API. При помощи Compact() API мы можем существенно перестроить базу данных, удалив из нее все неиспользуемые области. Совершенно очевидно, что такая операция будет весьма длительной для больших и сложных баз данных, поэтому ее следует проводить только когда это действительно нужно.

Другим параметром, влияющим на производительность базы данных, является ее структура. Для уменьшения нагрузки на файловую систему, компоненты баз данных весьма широко используют кэширование. При этом можно добиться еще большей производительности базы данных, если ее элементы будут кэшировать наиболее часто используемые элементы.

Представьте себе базу данных с таблицей записей, в каждой из которых имеется несколько маленьких и одно большое поле. Для наглядности представим, что чаще всего используются маленькие поля записи.

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

Если быстродействие для вашего приложения с базой данных является очень критичным, то возможно вы захотите измерить быстроту работы всех доступных вам вариантов реализации вашего решения. Как уже говорилось ранее, быстродействие приложений с базами данных в основном определяется выбором структуры базы, и алгоритмами доступа к данным, поэтому дополнительные усилия по определению наилучшего решения в конечном итоге окупятся вам с лихвой.

Если вы захотите провести общий тест производительности базы данных, имейте в виду, что каждая операция над базой данных имеет некоторый разброс по времени ее исполнения. Поэтому для определения настоящего значения времени выполнения операции потребуется провести определенное число опытов.

Плохое использование шаблонов проектирования

Шаблоны проектирования представляют собой удобный способ приведения проблем и задач, стоящих перед программой, в форму хорошо известных классов. Общие, испытанные и протестированные решения этих проблем затем можно легко реализовать в коде. Однако шаблоны проектирования никогда не должны заменять работу головы над решением проблемы и получению работающего кода.

Проблемы могут возникнуть даже если будет выбран некий определенный шаблон проектирования. Обычно шаблоны проектирования целиком описываются при помощи парадигмы объектно-ориентированного программирования, однако объектная абстракция не всегда применима на практике. Слепое следование шаблону может привести вас как потере производительности, так и к усложнению кода.

Рассмотрим следующий пример: в архитектуре было решено применить шаблон состояния (state pattern). Одним из способов реализации этого шаблона является наследование от общего базового класса специализированных классов для каждого состояния некоторого объекта. Для инициализации конечного автомата, разработчики выбрали фабричный шаблон (factory pattern):

CExampleStateFactory* CExampleStateFactory::NewL()
{
CExampleStateFactory* factory = new ( ELeave ) CExampleStateFactory();
CleanupStack::PushL( factory );

// Создать все состояния
factory->iStates[ EError ] = new ( ELeave ) TExampleStateError( *factory );
factory->iStates[ EStarted ] = new ( ELeave ) TExampleStateStarted( *factory );
factory->iStates[ EStopped ] = new ( ELeave ) TExampleStateStopped( *factory );

// И так далее...

CleanupStack::Pop();
return factory;
}

Фабрика состояний обладает всеми инициализированными объектами, каждый из которых содержит указатель на фабрику. Однако если мы более детально рассмотрим класс состояния:

class TExampleStateBase
{
public:
TExampleStateBase( CExampleStateFactory* aFactory );

inline TExampleStateBase* GetState( TStateEnum aState )
{
return iFactory->GetState( aState );
}

private:
CExampleStateFactory* iFactory;
}

TExampleStateBase::TExampleStateBase( CExampleStateFactory* aFactory )
: iFactory( aFactory )
{
}

TExampleStateStarted::TExampleStateStarted( CExampleStateFactory* aFactory )
: TExampleStateBase( aFactory )
{
}

Мы заметим, что указатель на фабрику, хранимый каждым из объектов состояний, используется только лишь в момент переключения состояния. Таким образом, благодаря шаблону состояний наш код весьма и весьма усложнился. Если объект фабрики будет очень интенсивно использоваться, это приведет к существенному снижению производительности. Так же весьма большое количество кода используется для инициализации фабрики, то в конечном итоге это приводит еще и к увеличению затрат на стороне ROM-памяти.

Если бы шаблон состояний был реализован несколько иначе, с переключением состояний, закодированным вне конечного автомата, тогда вся конструкция смогла бы избежать излишней сложности, и львиная доля фабричного шаблона была бы реализована собственными средствами компилятора.

Если каждый из объектов состояний является простым, и не содержит внутри данных, тогда весь конечный автомат мог быть реализован в виде таблицы указателей на виртуальные функции. В этом случае совершенно отпала бы необходимость реализации фабричного шаблона, а состояния конечного автомата изменялись бы при помощи приведения типов. Хотя стоит отметить, что при этом на порядок бы уменьшилась читаемость кода, и значительно бы возрос риск скрытых дефектов.

Обычный код, и код, создаваемый "на будущее"

Данная категория проблем возникает из-за излишнего использования фреймворков и плагинов. Проблемы этого типа так же подразумевают уменьшение быстродействия кода взамен легкости разработки приложения.

Представьте себе приложение, хранящее параметры конфигурации в файле ".ini". Во время разработки эти параметры очень часто меняются, поэтому их очень удобно хранить в форме, удобной для изменения. Однако по завершении разработок, эти параметры становятся постоянными, и теперь уже ресурсы и быстродействие приложения тратятся на считывание и разбор этих данных. В этом случае было бы неплохо применить какой-нибудь фреймворк или плагин от Symbian OS, созданный как раз для хранения параметров приложения, однако и в этом случае есть вероятность их неправильного использования.

Использование фреймворка приводит к увеличению размеров кода, так как в этом случае вам уже приходится искать плагины, выбирать их, проверять доступность, загружать и связываться с их DLL. Все это приводит к ухудшению производительности, так как фреймворки служат лишь внешними интерфейсами к плагинам, которым затем и передают команды от вашего приложения.

Такие потери еще вполне приемлемы, если для вашего приложения требуется определенная динамическая гибкость. Однако если из имеющегося набора плагинов за все время работы вашего приложения используется только один, такие затраты могут стать лишними.

Код, создаваемый "на будущее", так же может стать хорошей практикой, однако опять же, если имеющиеся инструменты будут использованы неправильно, это снова приведет к росту кода и потере производительности приложения.

Разработка и тестирование на эмуляторе

Эмулятор Symbian OS специально создавался как инструмент разработки, позволяющий немедленно перейти от написания кода к его исполнению. Эмулятор так же позволяет отладить код во время его разработки и исполнения, не используя при этом дорогих электронных прототипов.

В то же время, все эти достоинства становятся бессмысленными, если мы забудем о тестировании на настоящих устройствах, в особенности под горячку окончания сроков проекта. Очень часто тестирование на реальных устройствах ограничивается проверкой функциональности кода, либо проверкой работы очередной версии приложения. Все это может привести к плохому пониманию того как ваш код работает в реальном устройстве. А так как плохое понимание работы кода не всегда может прямо указать на проблемы производительности приложения, то обнаружение таких проблем переносится либо на финальные стадии разработок, либо на стадии выпуска продукта на рынок.

Благодаря недостаточному тестированию, или слабому контролю качества, все эти проблемы могут обнаружить ваши клиенты. И в конечном итоге, все это существенно ограничит вас в дальнейших возможностях менять архитектуру приложения, так как времени на исправление больших ошибок у вас уже не будет.

Вы, ваш компилятор и ваш код

Знание особенностей компилятора так же важно, как и знание используемой вами операционной системы и языка программирования. Многие из современных компиляторов имеют целый ряд оптимизирующих процессов, пытающихся произвести для вас код желаемого качества, будь то самый компактный или самый быстрый код. И для вас будет очень полезно знать какой код компилятор пытается произвести. Так же очень важно помнить, что "фокусы" одного компилятора не всегда будут повторяться на других компиляторах.

Не идите против компилятора

Вам следует знать особенности вашего компилятора, а так же то, какой код он генерирует от ваших исходников. Однако не пытайтесь давать компилятору слишком строгие указания. Такие указания могут заставить компилятор производить код особенным образом, который будет непригоден в некоторых случаях.

Выучите немного ассемблера

Ассемблер многими воспринимается как черная магия, и отвергается большинством разработчиков. Однако для полного понимания того как будет работать ваш код на конкретной платформе, поверхностное понимание ассемблера будет очень и очень полезным.

Маленькие советы

Помимо "убийц производительности", в данном буклете так же представлены маленькие советы, которые помогут вам создавать более эффективный код. Многие из этих советов носят общий характер, и детально рассмотрены в стандартах кодирования для Symbian OS. Некоторые из них все же стоит упомянуть прямо здесь.

Избегайте вызова функций внутри определения условия циклов

Избегайте вызовов функций внутри определения условий циклов. Вместо этого старайтесь сохранять результат, возвращаемый из такой функции, в локальной переменной, и использовать ее. По крайней мере в тех случаях, когда результат, возвращаемый функцией, не меняется на каждом шаге цикла.

Используйте ссылки или указатели там, где это нужно

Передача параметров через ссылки обычно представляет собой правило хорошего тона, однако при этом не стоит передавать целочисленные типы в виде ссылок, особенно если информация из них только считывается.

Не раскручивайте циклы

Для современных компиляторов оптимизации в виде размотки или раскрутки циклов уже не нужны. Более того, такие оптимизации могут больше навредить, чем помочь. Позвольте компилятору самому решить что и как оптимизировать в вашем цикле.

Избегайте длинных блоков из if и else

Лучше всего воспользоваться блоком switch, так как он может более эффективно выполнить эту работу. Если условия перехода не являются постоянными целочисленными типами, как например строки, тогда попробуйте до блока switch воспользоваться таблицей поиска (lookup table) для вычисления таких констант.

Используйте соответствующим образом квалификатор const

Благодаря определению только считываемых переменных в виде const, вы позволяете компилятору сгенерировать боле эффективный код.

Быстрый анализ кода при помощи User::FastCounter()

Библиотека пользователя при помощи метода User::FastCounter() дает возможность извлечь системное время с достаточной точностью, чтобы измерить скорость выполнения того или иного кода. Реализация этого метода зависит от устройства, однако при помощи API HAL:Get() можно узнать параметры этого счетчика: параметр EFastCounterFrequency дает возможность узнать частоту счетчика, а параметр EFastCounterCountsUp - направление отсчета, используемого в счетчике.

Инструменты: пошаговый анализатор

Данная глава предназначена для тех разработчиков, кто имеет доступ к лицензионным прототипам, определенным уровням SDK, или использует эталонные платы.

Пошаговый анализатор может использоваться для получения грубой, статической картины работы кода в реальном устройстве. Все, что он делает, - это каждую миллисекунду сохраняет в файле-логе ID работающего потока, и текущее значение счетчика команд (program counter). Для анализа сохраненных данных используется специальная программа, работающая из командной строки компьютера. Полученная таким образом информация может помочь в обнаружении проблем с производительностью программы, и помочь в пересмотре кода на предмет "узких мест".

Компиляция ROM-файла

Для того, чтобы было можно использовать анализатор, его нужно включить в состав ROM-файла. Делается это при помощи указания файла "profiler.iby" в строке с командой "buildrom":

buildrom h4hrp techview profiler.iby


Запуск анализатора

Самый простой способ запустить анализатор - это вызвать его в командной строке eshell:

start profiler start

Команда запустит отдельный поток, в котором будет работать анализатор. Благодаря этому потоку вы сможете переключаться на другие задачи при помощи комбинации клавиш Ctrl+Alt+Shift+T.

Запуск изучаемого кода

В момент запуска вашего приложения анализатор должен работать и записывать данные. Короткая пауза перед запуском вашего кода поможет вам визуально отделить данные изучаемого кода от всего остального, работающего в системе.

Остановка анализатора

После того, как вы получите желаемые данные, переключитесь обратно в командную оболочку eshell, и остановите анализатор:

profiler stop

А затем, закройте и файл, использовавшийся для сохранения получаемых данных:

profiler unload


Получение данных анализатора

В корне диска C вашего устройства вы должны обнаружить файл "profiler.dat". Для передачи его на компьютер, переместите его на карточку памяти вашего устройства.

Анализ данных

Вначале вам следует конвертировать данные из файла "profiler.dat" в формат, понятный программе Excel, так как именно в ней вы сможете создать график активности вашего кода.

Копирование файла с данными

Скопируйте файл "profiler.dat" в ту директорию, где хранятся ваши ROM-файлы. Это делается для возможности использования таблиц символов, использовавшихся для создания ROM-файлов, так как именно при помощи этих таблиц можно извлечь собранные данные. Запустите следующую команду:

analyse -r h4hrp_001.techview.symbol profiler.dat -v -mx > profile.xls


Создание графа активности

Откройте файл "profile.xls" в Excel-е. Удалите первые 6 рядов из таблицы, чтобы ваш граф показывал имена потоков. В первых шести рядах хранятся суммарные данные, которые не стоит добавлять в граф. Подобным образом значения времени, сохраненные в первом столбце, будут лишними для графа, однако удалять их не стоит, так как они будут служить перекрестными ссылками для тех областей данных, которыми вы интересуетесь.

Выделите все данные, и запустите помощника для создания графов. В открывшемся окошке проделайте следующее:

  • В качестве типа графа выберите "Area", а в качестве подтипа - "Stacked". Нажмите "Next".

  • Выберите область данных так, чтобы не затрагивать первый столбик. Измените столбик "А" на "B", то есть =profile!$A$2:$V$941 должно измениться на =profile!$B$2:$V$941. Нажмите "Next".

  • Ничего не делайте в открывшемся окошке, и просто нажмите "Next". В конце выберите "As new sheet", и нажмите "Finish".



Выберите активную область и потоки

Глядя на получившийся граф, вы должны начать понимать что и когда делала ваша программа. Вы так же можете навести мышку на выделенную область, и открывшееся окно расскажет что за поток работал в это время. Вы сможете так же использовать номер ряда в таблице для определения момента записи данных в файл. Если какие-то ряды в таблице вам не интересны, вы можете удалить их. Помните, что при удалении, Excel переномерует все ряды, поэтому лучше всего вначале удалять последние ряды. После каждого удаления граф будет перерисован.

Создание листинга по всем функциям

Как только вы узнаете время выполнения каждого потока, вы так же сможете создать список функций, упорядоченный по их активности. Например, для того чтобы узнать какие функции вызывались между моментами 51300 и 76900 в потоке EFile, нужно запустить следующую команду:

analyse -r h4hrp_001.techview.symbol profiler.dat -bf -p -s51300-76900 -t EFile* > analysis.txt

При этом следует обратить на следующие особенности:

  • При определении значений параметра "-s", между заданными значениями моментов времени не должно быть никаких пробелов.

  • Моменты времени берутся из первого столбика файла "profile.xls".

  • После параметра "-t" сначала идет пробел, и уж потом - имя искомого потока. В названии потока можно использовать символы для поиска как в его начале, так и конце.


Полученный результат можно будет посмотреть в любом текстовом редакторе. Вы должны будете увидеть список функций, вызванных в указанный промежуток времени. Обычно наибольший интерес представляют пять первых функций. После этого вы можете воспользоваться любым IDE, чтобы проверить код этих функций.

Ресурсы для разработчиков

Symbian Developer Network

Symbian Developer Network newsletter

Symbian OS Tools Providers

Sony Ericsson Developer World

Forum Nokia

Sun Microsystems Developer Services