«Наука начинается с тех пор, как начинают измерять. Точная наука немыслима без меры»
Д.И.Менделеев
Измерение производительности программ #
Зачем, что и как измерять? #
Измерение времени работы программы производится с целью ответа на вопрос: как долго будет работать программа с различными объемами входных данных, какой практический «потолок» использования она имеет. Для этого необходимо установить зависимость времени ее работы от объема входных данных. При этом нужно учитывать следующие важные моменты:
можно измерять время работы программы при помощи имеющихся в библиотеках функций. Если для этого использовать функцию текущего системного времени (clock), то мы получим «грязное» время с учетом всех других работ, выполняющихся в операционной системе. Кроме того, это время зависит от аппаратной и программной конфигурации, на которой производится измерение
в 4.1. было введено понятие трудоемкости: зависимость количества массовых операций в программе (например, сравнение, сдвиг, обмен) от размерности входных данных N. При оценке трудоемкости обычно используется грубая оценка степени роста полученной функции трудоемкости T(N), что справедливо при достаточно больших N. Но с какого именно значения N это соблюдается, в общем случае сказать невозможно;
для получения трудоемкости в программу включаются переменные-счетчики, изменяющие свое значение в той точке программы, где присутствует соответствующая операция;
время выполнения программы может меняться при ее работе с различными данными – это называется чувствительностью к данным. Для трудоемкости иногда бывает достаточно просто оценить пределы такой чувствительности, т.е. лучший и худший случаи. В каждом конкретном случае мы будем иметь некоторый случайный процесс, характеризующийся определенным разбросом;
измерения можно проводить разными способами. Можно, например, брать файлы различных размеров и измерять итоговые характеристики, а можно получать значения измеряемых величин на одном большом файле через m равных значений размерности.
В качестве примера посмотрим, как выглядит процесс сбора и обработки данных на программе, читающей пословно файл и включающей слова в динамический массив указателей с сохранением порядка. Сразу же заметим, что трудоемкость по операциям сравнения имеет вид T=N2/4, в лучшем случае, при вставке прямо упорядоченного текста, T=N2/2, в лучшем случае T=N – если входной текст обратно упорядочен. В качестве входного файла используется текст литературного произведения, имеющий достаточно «случайный» вид.
C
//------------------------------------------------------62-06.cpp//------- Создание упорядоченного ДМУ из строк файла#include<stdio.h>#include<string.h>#include<malloc.h>#include<time.h>// Для функции clock#include<windows.h>// Для функции CharToOemA#defineSIZE0100char**loadfile(FILE *fd){int cnt=0;// Счетчик сравненийlong T0=clock();// Начальное значение времениchar str[1000];// в миллисекундахint i,j,n=0,sz=SIZE0;// Кол-во строк и размерность ДМУchar**pp = new char*[sz];// Создать ДМУwhile(fscanf(fd,"%s",str)==1){for(i=0;i<n;i++){
cnt++;// Увеличение счетчика сравненийif(strcmp(str,pp[i])<0)break;}for(j=n-1;j>=i;j--)
pp[j+1]=pp[j];
pp[i]=strdup(str);// Копия строки в ДМ
n++;// Вывод статистики на каждые 5000 словif(n%5000==0)printf("%d\t%d\t%ld\n",n,cnt,clock()-T0);if(n+1==sz){// Будет переполнение -
sz*=2;// удвоить размерность
pp=(char**)realloc(pp,sizeof(char*)*sz);}}
pp[n]=NULL;// Ограничитель массива указателейprintf("%d\t%d\t%ld\n",n,cnt,clock()-T0);return pp;}voidmain(){int i;
FILE *fd=fopen("text.txt","r");char cc[80];char**pp=loadfile(fd);puts("---------------------------------------------");gets(cc);for(i=0; pp[i]!=NULL; i++){CharToOemA(pp[i],cc);// Преобразование кодировки для выводаprintf("%s\n",cc);}for(i=0; pp[i]!=NULL; i++) delete []pp[i];
delete []pp;fclose(fd);}
Проведение измерений и сохранение результатов #
Значения счетчика операций сравнения cnt и «грязного» времени работы программы clock()-T0 выводятся в окно консольного приложения, в принципе, их легко можно было бы перенаправить в тестовый файл. При форматном выводе в качестве разделителей используются знаки табуляции (\t), чтобы облегчить последующий импорт данных в Excel. Для переноса данных из окна консольного приложения необходимо правой кнопкой мыши в левом верхнем углу окна вызвать контекстное меню, в котором последовательно выполнить команды «изменить – пометить(выделить все)» и «изменить-копировать», затем скопированные в буфер обмена данные вставить в окно текстового файла.
Для включения полученных данных в Excel необходимо выполнить последовательность выбором меню и действий мастера:
«Данные»;
«Импорт внешних данных»;
«Импортировать внешние данные»;
Выбор текстового файла в диалоговом окне;
«Формат данных – с разделителями» в мастере импорта;
«Символы-разделители – табуляция и пробел» в мастере импорта;
«Формат данных столбца - общий» в мастере импорта;
Выбрать ячейку диапазона, куда будут импортированы данные.
Оценка полученных зависимостей #
Для грубой оценки полученной зависимости необходимо разместить полученные данные в электронной таблице Ozenka.xls. Оценка производится следующим образом:
В первый столбец таблицы вносится размерность входных данных (число слов);
Во второй столбец вносится экспериментальное значение (в первую таблицу – время работы, во вторую – количество операций сравнения).
Идея оценки очень проста. Проверяется, насколько экспериментальные данные F1(N) соответствуют виду степенной функции F(N)=KNP. Показатель степени P подбирается опытным путем, исходя из предварительного анализа трудоемкости программы: в нашем случае простая упорядоченная вставка должны давать квадратичную зависимость. Коэффициент пропорциональности K подбирается таким образом, чтобы значения экспериментальной и математической зависимости совпали в некоторой точке (N0) – можно взять одну из первых точек N0=5000, тогда F1(N0)=F(N0)= KN0P, откуда следует, что K= F1(N0)/( N0P).
Естественно, если ожидаемый вид функциональной зависимости будет другим (например, линейно-логарифмическим для эффективных алгоритмов сортировки), то формулы, где присутствует эта функция, необходимо скорректировать.
N
cmp(эскп)
cmp*(матем)
отклонение
коэф по N1
Степень
5000
6217209
6217209
0%
0,248688
2
10000
24945611
24868836
0%
15000
57045716
55954881
2%
20000
100133518
99475344
1%
25000
156160970
155430225
0%
30000
225396760
223819524
1%
35000
304565494
304643241
0%
40000
396710798
397901376
0%
45000
501222764
503593929
0%
50000
621272463
621720900
0%
55000
754949505
752282289
0%
60000
897124300
895278096
0%
65000
1049094335
1050708321
0%
70000
1220253187
1218572964
0%
75000
1401604268
1398872025
0%
80000
1597098280
1591605504
0%
85000
1803745315
1796773401
0%
90000
2025588151
2014375716
1%
91479
2088624766
2081125522
0%
Какие же выводы можно сделать из полученных результатов:
Количество операций сравнения практически точно имеет зависимость T(N)=N2/4 (K=0.25 N=2), что соответствует нашим предварительным теоретическим оценкам;
«Грязное» время работы программы подчиняется степенной зависимости с показателем P=2.4, т.е. примерно N2√N, т.е. хуже, чем квадратичная. Отклонение сначала достигает 30% в лучшую сторону, а затем меняется на 20% в худшую сторону, постепенно снижаясь с увеличением длины файла. Очевидно, что при работе программы имеют место процессы (ввод-вывод, работа с динамической памятью), которые имеют характеристики худшие, чем используемый в программе алгоритм.