Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
686.docx
Скачиваний:
175
Добавлен:
24.04.2019
Размер:
6.68 Mб
Скачать

3.3.4.1 Цели и задачи профилировки

Основная

цель

профилировки

исследовать

характер

поведения

программы во всех её точках. Под «точкой», в зависимости от степени

детализации, может подразумеваться как отдельная машинная команда, так

целая конструкция языка высокого уровня (например, функция, цикл или одна–

единственная строка исходного текста).

Большинство современных профилировщиков поддерживают следующий

набор базовых операций:

 определение общего времени исполнения каждой точки программы

(total [spots] timing);

 определение удельного времени исполнения каждой точки программы

([spots] timing);

 определение причины и/или источника конфликтов и пенальти (penalty

information);

 определение количества вызовов той или иной точки программы

([spots] count);

 определение степени покрытия программы ([spots] covering).

3.3.4.2 Общее время исполнения

Сведения о времени, которое тратится на выполнение каждой точки

программы, позволяют выявить её наиболее «горячие» участки. Правда, здесь

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

крайней мере, 99,99% всего времени выполнения профилируемая программа

проводит внутри функции main, при этом «горячей» является отнюдь не сама

main, а вызываемые ею функции. Чтобы не вызывать у программистов

недоумения, профилировщики обычно вычитают время, потраченное на

выполнение дочерних функций, из общего времени выполнения каждой

функции программы.

160

3.3.4.3 Удельное время выполнения

Если время выполнения некоторой точки программы не постоянно, а

варьируется в тех или иных пределах (например, в зависимости от рода

обрабатываемых данных), то трактовка результатов профилировки становится

неоднозначной, а сам результат – ненадежным. Для более достоверного анализа

требуется: а) определить действительно ли в программе присутствуют

подобные «плавающие» точки и, если да, то: б) определить время их

исполнения в лучшем, худшем и среднем случаях.

3.3.4.4 Определение количества вызовов

Оценивать температуру точки можно не только по времени ее выполнения,

но и частоте вызова. Например, пусть у нас есть две «горячие» точки, в

которых процессор проводит одинаковое время, но первая из них вызывается

сто раз, а вторая – сто тысяч раз. Нетрудно догадаться, что, оптимизировав

последнюю хотя бы на 1%, мы получим колоссальный выигрыш в

производительности, в то время как, сократив время выполнения первой из них

вдвое, мы ускорим нашу программу всего лишь на четверть.

Таким образом, часто вызываемые функции в большинстве случаев имеет

смысл «инлайнить» (от английского in-line), т. е. непосредственно вставить их

код в тело вызываемых функций, что сэкономит какое-то количество времени.

3.3.4.5 Определение степени покрытия

Покрытие – это процент реально выполненного кода программы в

процессе его профилировки. Такая информация, в первую очередь, нужна

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

ней не осталось никаких «темных» мест. С другой стороны, оптимизируя

программу, очень важно знать, какие именно ее части были профилированы, а

какие нет. В противном случае, многих «горячих» точек можно просто не

заметить только потому, что соответствующие им ветки программы вообще ни

разу не получили управления.

3.3.5

Анализ исходного кода

Анализатор кода (code analyzer, code reviewing software) – программное

обеспечение (ПО), которое помогает обнаружить ошибки (уязвимые места) в

исходном коде программы.

Анализ кода (code analysis) – это близкий родственник обзора кода (code

review).

161

Code review – это систематическая проверка исходного кода, проводящаяся

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

улучшить качество программы и навыки разработчиков.

Обычно code review включает участие:

 человека, который код писал;

 человека (или людей), которые этот код могут читать и понимать,

насколько хорошо он удовлетворяет общим и конкретным критериям.

Общие критерии представляют собой стандарты кодирования (coding

standard). Конкретные критерии подразумевают знания требований, для

удовлетворения которых код написан.

Процедура анализа кода отличается от тестирования. При тестировании

программа проверяется на некотором наборе входных данных с целью

выявления

несоответствия

действительного

поведения

программы

специфицированному. Однако спецификация может определять поведение

программы лишь на подмножестве множества всех возможных входных

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

тестирования. Для этого и нужно проводить анализ кода, который позволяет

обнаружить такие ошибки, а точнее уязвимые места исходного кода:

переполнение буферов, неинициализированная память, указатели (null-pointer),

«утечка» памяти, состояние гонок и др. Примеры такого рода ошибок можно

увидеть в статье, посвященной статическому или динамическому анализу кода.

Можно сказать, что анализ исходного кода – это процесс получения

информации о программе из ее исходного кода или объектного кода. Исходный

код – это статическое, текстовое, удобочитаемое, исполняемое описание

компьютерной

программы,

которое

может

быть

скомпилировано

автоматически в исполняемый код.

Анализ исходного кода включает три компонента:

1. Парсер (parser), который выполняет синтаксический разбор исходного

кода и преобразует результаты анализа в одну или несколько форм

внутреннего представления. Большинство парсеров (синтаксических

анализаторов) основываются на компиляторах.

2. Внутреннее представление, которое абстрагирует конкретный аспект

программы и представляет его в форме, пригодной для выполнения

автоматического

соответствующими

анализа.

типами

Например,

данных.

переменные

Некоторые

заменяются

внутренние

представления создаются непосредственно парсерами, другие требуют

результатов предыдущего анализа. Классическими примерами таких

представлений является граф потоков управления (control-flow graph,

CFG), дерево вызовов (call graph), абстрактное синтаксическое дерево

(abstract syntax tree, AST),

форма

статического

единственного

присваивания (static single assignment, SSA).

162

3. Анализ внутреннего представления. Анализ может производится по

различным направлениям:

 Статический или динамический. Статический анализ кода (static code

analysis)

производится

без

реального

выполнения

программ.

Результаты такого анализа применимы и одинаковы для всех

исполнений программы. В отличие от статического анализа,

динамический анализ кода (dynamic code analysis) производится при

помощи выполнения программ на реальном или виртуальном

процессоре.

Результаты

такого

анализа

более

точные,

но

гарантируются только для конкретных входных данных. Утилиты

динамического анализа могут требовать загрузки специальных

библиотек или даже перекомпиляцию программного кода.

 Контекстно-зависимый (context-sensitive) межпроцедурный анализ.

 Потоково-зависимый (flow-sensitive) анализ.

Кроме того, анализаторы кода можно классифицировать следующим

образом.

 Автоматические анализаторы, которые проверяют исходный код в

соответствии с предопределенным набором правил и по результатам

проверки создают отчеты.

 Различные

виды

браузеров,

которые

визуализируют

структуру

(архитектуру) ПО, таким образом, помогают лучше ее понять.

Примером ошибок в программе, которые может обнаружить статический

анализатор, являются ошибки, связанные с переносом программ на 64-битные

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

Другие примеры можно найти в различных статьях [36].

При расчете необходимой для массива памяти использовался явно

размер типа элементов. На 64-битной системе этот размер изменился, но код

остался прежним:

size_t ArraySize = N * 4;

intptr_t *Array = (intptr_t *)malloc(ArraySize);

Некоторая функция возвращала значение -1 типа size_t в случае ошибки.

Проверка результата была записана так:

size_t result = func();

if (result == 0xffffffffu) {

// error

}

На 64-битной системе значение -1 для этого типа выглядит уже по-другому

и проверка не срабатывает.

Арифметика с указателями – постоянный источник проблем. Но в случае

с 64-битными приложениями к уже известным добавляются новые проблемы.

163

Рассмотрим пример:

unsigned short a16, b16, c16;

char *pointer;

pointer += a16 * b16 * c16;

Как видно из примера, указатель никогда не сможет получить приращение

больше

4

гигабайт,

что

хоть

и

не

диагностируется

современными

компиляторами как ошибка, но приведет в будущем к неработающим

программам. Можно привести значительно больше примеров потенциально

опасного кода.

Все эти и многие другие ошибки были обнаружены в реальных

приложениях во время переноса их на 64-битную платформу.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]