Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
53
Добавлен:
03.05.2021
Размер:
432.54 Кб
Скачать

9 лекция.

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

Теперь знакомимся еще с одним методом оценки сложности по циклам.

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

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

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

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

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

профилирования потоков можно использовать удобную API-функцию

GetThreadTimes:

BOOL GetThreadTimes(

HANDLE hThread, //дескриптор потока (определяется функцией GetCurrentThread)

FILETIME ftCreationTime, // время создания потока FILETIME ftExitTime, // время завершения потока

FILETIME ftKernelTime, // время работы потока в режиме ядра FILETIME ftUserTime); // время работы потока в режиме пользователя

Ну и пример использования этой функции. Порядок действий (пример):

1.Объявляем три переменные типа FILETIME: FILETIME ftKernelTime;

FILETIME ftUserTime; FILETIME ftDummy;

2.Объявляем три переменные типа _int64:

_int64 qwKernelTime, qwUserTime, qwTotalTime;

3. Вызываем функцию GetThreadTimes: GetThreadTimes(GetCurrentThread, &ftDummy, &ftDummy, &ftKernelTime, &ftUserTime);

4. Определяем время выполнения потока: qwKernelTime = FileTimeToQuadWord(&ftKernelTime); qwUserTime = FileTimeToQuadWord(&ftUserTime); qwTotalTime = qwKernelTime + qwUserTime;

printf("\n\nВремя выполнение первого потока: %lld", qwTotalTime)

------------------------------------------------------------------------------------------------------

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

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

Безусловно надо знать, чем занимается ядро.

-------------------------------------------------------------------------------------------------------

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

Для этого надо знать простые понятия по памяти.

------------------------------------------------------------------------------------------------------

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

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

-----------------------------------------

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

ограничение количества одновременно выполняемых задач;

отсутствие механизма защиты адресного пространства одной задачи от адресного пространства другой задачи и от адресного пространства ядра;

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

------------------------------------------------

Имя в пространстве имен программы - это идентификатор.

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

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

Это серьезные проблемы, которые непонятно как обходить.

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

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

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

------------------------------------------------

Напомню, что понятие виртуального адресного пространства вводилось в

разделе со сравнением процессов и потоков.

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

Виртуальное адресное пространство — это как раз пространство виртуальных адресов.

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

Это сопоставление выполняется системой программирования.

А потом уже виртуальные адреса сопоставляются с физическими адресами. Это сопоставление выполняется операционной системой на аппаратном уровне

спомощью устройства управления памятью (Memory Management Unit, MMU). Ну и вкратце рассмотрим работу с виртуальной памятью на уровне использования функции VirtualAlloc. Мы ее не будем использовать на практике, сразу говорю.

------------------------------------------------------------------------------------------------------

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

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

---------------------------------------------------------------------

Ключевые термины - это гранулярность и размер страницы.

Пример. Для процессоров Pentuim размер страницы составляет 4 Кб. Если задача попытается зарезервировать 10 Кб, то будет выделен регион размером 12 Кб.

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

----------------------------------------------------------------------

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

Эта схема аналогична бронированию номера и фактической передаче номера при заселении.

То есть номер в гостинице надо сначала забронировать, а при заселении фактически в него заселиться.

Здесь такая же схема.

Ну и рассмотрим функцию VirtualAlloc.

Из этого материала надо вынести определения гранулярности и размера страницы.

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

Эту функцию надо освоить в обязательном порядке. И показать мне, что вы знаете, как она вызывается.

На компьютере.

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

Ну и последнее, что сегодня рассмотрим. Вторичная память, которая имитирует память за счет использования дискового пространства, располагается в страничном файле или файле подкачки (paging file). Операционная система во взаимодействии с процессором сбрасывает содержимое части оперативной памяти в страничный файл и по мере необходимости подгружает (подкачивает) его порции обратно в память.

-------------------------------------------------------------------------

Здесь надо дополнительно кое-что написать. Подкачка непредсказуема по времени выполнения. Есть масса факторов, от которых она зависит.

Например, скорость обращения к основной памяти, скорость обращения ко вторичной памяти.

Кроме того, важное значение имеет фрагментация вторичной памяти. Я вкратце опишу, что такое фрагментация.

Ну буквально на пальцах.

Итак, представим, что в коде прописано множество идентификаторов, которые должны быть размещены в памяти на момент выполнения.

Для этого множества нужно в памяти найти свободную область.

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

При дискретном распределении памяти множество распределяется на несмежным (не соседним) участкам памяти.

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

Как правило, это объем меньше размера участка и возникает свободное (незанятое) место (разность между объемом участка и объемом множества

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

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

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

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

Такая в общем и целом хорошо известная беда.

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

Может быть, кого-то и миновала участь сия, но у меня жесткие диски падали не раз, не два и не три.

Это всегда неприятно и может быть чревато тяжелыми последствиями.

--------------------------------------------------------------------------------------------

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

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

Вот такая есть особенность.

Соседние файлы в папке АОПИ. Лекции