Анализ энергопотребления при выполнении операций ввода/вывода дисковой подсистемы

Введение

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

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

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


Изучение методов оптимизации энергопотребления

Характеристики диска

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

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

В таблице, приведенной ниже, показано относительное время выполнения операций (в миллисекундах). Данные основаны на теоретической спецификации SATA диска. Так как реальные цифры будут различаться для разных дисков, данный пример дает нам лишь примерное представление о времени выполнения операций.

(Характеристики платформы: Intel® Core™ Duo 2.0GHz, Jamison Canyon* CRB, 2x512MB DDR2, 40GB SATA 5400RPM-2.5’’ mobile, Windows* XP-SP2)

Seek Time Ave (ms) Latency Time Ave (ms) Spindle Start Time (ms)
12 5 .56 4000

 

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

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

API-интерфейсы Windows*

Интерфейс Win32 * предлагает различные API для операций ввода/вывода, но все вызовы чтения/записи в конечном итоге проходят через ReadFile () / WriteFile () API из kernel32.dll. Приложения могут выполнять операции ввода/вывода как в синхронном, так и в асинхронном режиме. Кроме того, Win32 предоставляет возможность использовать различные флаги, что помогает контролировать буферизацию файловой системы, а также дает ОС подсказки касательно типов ввода/вывода (например, последовательный или случайный).

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

Влияние размера блока на время последовательного чтения

Рассмотрим влияние размера блока на время последовательного чтения, принимая во внимание общую производительность и энергопотребление. Мы создали большой файл (около 1Гб) и считываем этот файл блоками различных размеров. Для того чтобы достичь одинаковых условий выполнения операций ввода/вывода, мы перезагружаем систему от запуска к запуску, таким образом избегая любых помех в кэше файловой системы. Ниже приведен код, используемый для данного эксперимента с чтением:

HANDLE hFile = CreateFile(szLargeFile, GENERIC_READ, 0,

NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);


unsigned int filesize = GetFileSize(hFile,(LPDWORD)0);


totalread = 0;
	
nrem = filesize;


for (totalread = 0; totalread <filesize; totalread += chunksize)

{

if (nrem >= chunksize) 

{

ReadFileChunk(hFile,pAlignedBuf,chunksize);

}

else

{

ReadFileChunk(hFile,pAlignedBuf,nrem);

}

nrem -= chunksize;

}

 

На графике, приведенном ниже, показана зависимость размера блоков на пропускную способность последовательного ввода/вывода и использование CPU:

The bandwidth significantly increases as we change from smaller block sizes to larger ones, and the corresponding processor utilization reduces as we move from larger block reads. The following table shows the relative comparison o n the total energy consumed for reading the large file with various block sizes (energy was calculated for the duration of the total read times using the NetDAQ* methodology mentioned before):

CPU_ENERGY (mWH) DISK_ENERGY (mWH)
1b 9658.77 1056.0
8b 1336.18 192.32
1KB 24.93 13.76
4KB 24.05 13.56
8KB 23.27 13.23
16KB 22.46 12.98
32KB 22.49 12.85
64KB 22.50 12.96

 

Очевидно, что разница между блоком чтения в 1b и большим блоком очень существенна, однако, разница между различными большими блоками не столь велика. Это связано с тем, что значения были рассчитаны в МВтч и работа выполняется за небольшое количество времени в случае с большими блоками. Таким образом, видно, что большие блоки идеальны как в отношении энергопотребления, так и в отношении производительности.


Рекомендации по последовательному чтению

Для достижения наилучшей производительности рекомендуется использовать в приложениях 8KB-блок. Это может подразумевать большую буферизацию данных или объединение нескольких запросов в таких приложениях, как, например, воспроизведение медиа. Также это позволяет снизить частоту вращения диска, что может быть необходимо, когда данные считываются меньшими кусками. При считывании большими кусками значительно увеличивается время вращения диска, а это, в свою очередь, увеличивает энергопотребление. Как видно на приведенном выше графике, после определенного порога (в данном случае, 8KB) скорость передачи данных меняется незначительно, поэтому выбирать блок большего размера не рекомендуется.

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

Как правило, стандартный MP3-плеер при воспроизведении музыкального MP3-файла считывает его с жесткого диска несколькими небольшими порциями (~ 2 Кб для файла размером 4 Мб). Жесткий диск при этом занят выполнением множества мелких операций чтения, поэтому среднее энергопотребление жесткого диска выше 1.25W (см. ниже).

Руководствуясь тем, что буферизация данных и объединение нескольких операций чтения уменьшают энергопотребление жесткого диска, мы скопировали MP3-файл в виртуальную память. Как видно на графике ниже, при начальной буферизации MP3-файла жесткий диск активен, а, когда все данные помещены в буфер и происходит чтение из памяти, жесткий диск бездействует и средний уровень его энергопотребления значительно снижается - до 0,8 Вт.

Влияние фрагментации файлов

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

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

for (int i=0; i>MAX_SIZE; i += 64KB)

{

AppendFile(“Fragmented_File0”,64KB,...);

AppendFile(“Fragmented_File1”,64KB,...);

}


void AppendFile(char *szFilename,...)

{

...

HANDLE hFile = CreateFile(szFilename,  
   
FILE_APPEND_DATA,

FILE_SHARE_READ,

NULL,OPEN_ALWAYS,

FILE_ATTRIBUTE_NORMAL, 

NULL);



(void)SetFilePointer(hFile, 0, NULL, FILE_END);

WriteFile(hFile,...);

CloseHandle(hFile);

...

}

 

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

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

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


Рекомендации по избежанию фрагментации

Фрагментация возникает при увеличении размера файла. Ее можно избежать, выделив больше пространства для файлов при их создании. Сложность состоит в том, чтобы заранее оценить размер файла и дать файловой системе возможность оптимально выделить максимально возможное пространство, чтобы содержимое файла находилось в смежных кластерах. Если вы используете .NET Framework *, SetLength () может быть применен, как показано ниже.

FileStream fs = new FileStream(filename,FileMode.OpenOrCreate);
fs.SetLength(256*1024);

 

Энергозатраты на дефрагментацию файла значительно ниже, чем потребление энергии при постоянном обращении к фрагментированному файлу. Один из способов избежания фрагментации – периодическое использование дефрагментаторов. Microsoft Windows* предоставляет несколько недокументированных интерфейсов для обнаружения фрагментированных файлов и их дефрагментации. NtFsControlFile() является основной функцией, которая может быть применена для определения количества кластеров в заданном файле и логических номеров кластеров (LCNs) для нахождения и выделения свободных больших кластеров, а также для перемещения фрагментированных файлов. Разработчики могут применять эти API, однако, это сопряжено с определенным риском, так как они не задокументированы в ОС.

Влияние NCQ на случайное чтение

NCQ (англ. Native command queuing) – это технология оптимизации порядка выполнения команд, которая зачастую позволяет добиться значительного улучшения параметров производительности и энергопотребления. Эксперименты проводились на диске SATA NCQ, однако, при работе с другими протоколами можно ожидать аналогичных улучшений. NCQ, известный как "лифтовый алгоритм", оптимально упорядочивает команды с целью сокращения временных издержек на поиск и вращение диска. С NCQ привод использует текущее положение головок чтения/записи и определяет наиболее эффективный путь выполнения всех команд, что напоминает принцип работы лифта. В отсутствие оптимизации порядка выполнения команд, диск будет выполнять команды в порядке поступления, что может привести к большому количеству вращений диска, а это вызывает снижение производительности и увеличение энергопотребления. Больше о NCQ можно узнать здесь.

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

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

int chunksize = 64*1024;

for (int i=0; i>nFiles; ++i)

{

HANDLE hFile = CreateRandomFile();

ReadFile(hFile,...); //blocked I/O

}


HANDLE CreateRandomFile()

{

...

int n = GetRandomNumber(0,file_max-1);

HANDLE hFile = CreateFile(szFilename[n], GENERIC_READ, 0,

NULL, OPEN_EXISTING,
 
FILE_ATTRIBUTE_NORMAL,NULL);

int filesize = GetFileSize(hFile,0);

int  offset = GetRandomNumber(0,filesize-chunksize);

SetFilePointer(hFile,offset,NULL,FILE_BEGIN); 

...


return hFile;


}

 

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

int chunksize = 64*1024;

HANDLE gPort;

for (int i=0; i>nFiles; ++i)

{

HANDLE hFile = CreateRandomFile();

QueueRead(hFile,gPort,...); //blocked I/O

}


While (nRead > nFiles)

{

GetQueuedCompletionStatus(gPort,...);

...

...

++nRead;

}

 

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

Рекомендации по использованию NCQ

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


Операции ввода/вывода на диске в многопоточном коде

Рассмотрим операции ввода/вывода в многопоточном коде, а также лучшую стратегию действий в случае, когда несколько потоков выполняют операции ввода/вывода на диске. Для данного анализа авторы статьи разработали программу, которая конвертирует некоторое множество растровых изображений в формат JPEG. Ниже представлена последовательная версия приложения. Окно “read” означает чтение растрового изображения в 64КБ блоках (те же размеры блоков используются и для записи в .jpg формат). Время считывания/записи занимает около 41% от общего времени, в то время как конвертирование занимает около 59% времени. Выходные JPEG файлы достаточно малы по сравнению файлами для чтения (каждое растровые изображения - около 10МБ), поэтому можно считать, что большая часть времени чтения/записи уходила на чтение.


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

Другой способ организации многопоточности – выполнение операций чтения-записи в одном потоке, а затем обработка трудоемких операций конвертирования в нескольких потоках:

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

На графике показана производительность этих многопоточных методов:

Показатели производительности увеличиваются в 3-м и 4-м случае (~1.52x-1.56x), а в случае, когда операции ввода/вывода также выполняются в многопоточном режиме, выигрыш отсутствует из-за прокрутки диска конкурирующими потоками. Когда 2 потока одновременно пытаются считывать 64КБ данные, возникают значительные дополнительные затраты времени на поиск и вращение диска. Так как должны быть выполнены запросы на чтение каждого потока, головка чтения должна перемещаться после каждого прочтения блока данных. Это приводит к неоптимальным вращениям диска и увеличению общего времени считывания данных, что нивелирует выигрыш от использования многопоточности. Приведенный ниже график энергопотребления показывает, что 4-й случай является лучшим как в плане энергопотребления, как и в плане производительности:


Заключение

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


Ссылки

 


Об авторах

Karthik Krishnan - специалист по приложениям, работает в Software and Solutions group компании Intel. Пришел в Intel в 2001 году, в настоящее время ведет работу с различными поставщиками ПО в области оптимизации их продуктов для мобильных и настольных платформ Intel®. До прихода в Intel работал в Fluent Inc. как разработчик программного обеспечения с использованием параллельного программирования.

Jun De Vega – специалист по приложениям, работает в Software and Solutions group компании Intel. Занимается настройкой и оптимизацией приложений для архитектуры Intel®, обеспечивает поддержку приложений независимых поставщиков на мобильных и настольных платформах Intel®.

 


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