Windows для профессионалов

       

Выравнивание данных


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

Процессоры работают эффективнее, когда имеютдело с правильно выровненными данными. Например, значение типа WORD всегда должно начинаться с четного адреса, кратного 2, значение типа DWORD - с четного адреса, кратного 4, и т. д. При попытке считать невыровненные данные процессор сделает одно из двух: либо возбудит исключение, либо считает их в несколько приемов.

Вот фрагмент кода, обращающийся к невыровненным данным:

VOID SomeFunc(PVOID pvDataBuffer)
{

// первый байт в буфере содержит значение типа BYTE
char с = * (PBYTE) pvDataBuffer;

// увеличиваем указатель для перехода за этот байт
pvDataBuffer = (PVOID)((PBYTE) pvDataBuffer + 1);

// байты 2-5 в буфере содержат значение типа DWORD
DWORD dw = * (DWORD *) pvDataBuffer;

// на процессорах Alpha предыдущая строка приведет к исключению
// из-за некорректного выравнивания данных

...

}

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

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

Рассмотрим, как справляется с выравниванием данных процессор типа x86. Такой процессор в регистре EFLAGS содержит специальный битовый флаг, называемый флагом AC (alignment check). По умолчанию, при первой подаче питания на процессор он сброшен Когда этот флаг равен 0, процессор автоматически выполняет инструкции, необходимые для успешного доступа к невыровненным данным. Однако, если этот флаг установлен (равен 1), то при каждой попытке доступа к невыровненным данным процессор инициирует прерывание INT 17h. Версия Windows 2000 для процессоров типа x86 и Windows 98 никогда не изменяют этот битовый флаг процессора.



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

Теперь обратим внимание на процессор Alpha. Он не умеет оперировать с невыровненными данными. Когда происходит попытка доступа к таким данным, этот процессор уведомляет операционную систему. Далее Windows 2000 решает, что делать — генерировать соответствующее исключение или самой устранить возникшую проблему, выдав процессору дополнительные инструкции. По умолчанию Windows 2000, установленная на компьютере с процессором Alpha, сама исправляет все ошибки обращения к невыровненным данным. Однако Вы можете изменить ее поведение. При загрузке Windows 2000 проверяет раздел реестра:

HKEY_LOCAL_MACHINE\CurrentControlSet\Control\Session Manager

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

Чтобы упростить изменение этого параметра реестра, с Microsoft Visual C++ для платформы Alpha поставляется утилита AXPAlign.exe. Она используется так, как показано ниже.

Alpha AXP alignment fault exception control Usage axpalign [option]

Options:

/enable to enable alignment fault exceptions

/disable to disable alignment fault exceptions.

/show to display the current alignment exception setting

Enable alignment fault exceptions.

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


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

Заметьте, что SetErrorMode(SEM_HOALIGNMENTFAULTEXCEPT) позволяет подавить генерацию таких исключений даже в этом режиме.

Disable alignment fault exceptions

Этот режим действует no умолчанию в Windows NT for Alpha AXP версий 3.1 и 3.5 Операционная система сама исправляет любые ошибки связанные с доступом к невыровненным данным (если таковые ошибки возникают) и приложения или отладчики их не замечают Если программа часто обращается к невыровненным данным производительность ситемы может заметно снизиться Для наблюдения за частотой появлония таких ошибок можно использокать Perfmon или wperf

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

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

UINT SetErrorMode(UINT fuErrorMode);

В данном случае Вам нужен флаг SEM_NOAUGNMENTFAULTEXCEPT Когда он установлен, система автоматически исправляет ошибки обращения к невыровненным данным, а когда он сброшен, система вместо этого генерирует соответствующие исключения Заметье, чю изменение этого флага влияет на потоки только того про цесса, из которого была вызвана функция SetErrorMode Иначе говоря, его модификация не отражается на потоках других процессов Также учтите, что любые флаги режимов обработки ошибок наследуются всеми дочерними процессами Поэтому перед вызовом функции CreateProcess Вам может понадобиться временно сбросить этот флаг

SetErrorMode можно вызывать с флагом SEM_NOALIGNMENTFAULTEXCEPT независимо от того, на какой платформе выполняется Ваше приложение Но результаты ее вызова не всегда одинаковы На платформе x86 сбросить этот флаг просто нельзя, а на платформе Alpha его разрешается сбросить, только если параметр EnableAlignmentFaultExceptions в реестре равен 1



Для наблюдения за частотой возникновения ошибок, связанных с доступом к невыровненным данным, в Windows 2000 можно использовать Performance Monitor, подключаемый к MMC На следующей иллюстрации показано диалишвое окно Add Counters, которое позволяет добавить нужный показатель в Performance Monitor



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

Как видите, простого вызова SetErrorMode вполне достаточно для того, чтобы Ваше приложение работало корректно. Но это решение явно не самое эффективное Так, в AlphaArchitectureReferenceManual, опубликованном Digital Рress, утверждается, что системный код, автоматически устраняющий ошибки обращения к невыровненным данным, может снизить быстродействие в 100 раз! Издержки слишком велики К счастью, есть более эффективное решение этой проблемы.

Компилятор Microsoft С/С++ для процессоров Alpha поддерживает ключевое слово _unaligned Этот модификатор используется так же, как const или volatile, но применим лишь для переменных-указателей Когда Вы обращаетесь к данным через невыровненный указатель (unahgned pointer), компилятор генерирует код, исходя из того, что данные скорее всего не выровнены, и вставляет дополнительные машинные инструкции, необходимые для доступа к таким данным. Ниже показан тот же фрагмент кода, что и в начале раздела, но с использованием ключевого слова _unaligned

VOID SomeFunc(PVOID pvDataBuffer)
{

// первый байт в буфере содержит значение типа BYTE
char с = * (PBYTE} pvDataBuffer;

// увеличиваем указатель для перехода за этот байт


pvDataBuffer = (PVOID)((PBYTE) pvDataBuffer + 1);

// байты 2-5 в буфере содержат значение типа DWORD
DWORD dw = * (__unaligned DWORD *) pvDataBuffer;

// Предыдущая строка заставит компилятор сгенерировать дополнительные
// машинные инструкции, которые позволят считать значение типа DWORD
// в несколько приемов При этом исключение из-за попытки доступа
// к невыровненным данным не возникнет

}

При компиляции следующей строки на процессоре Alpha, генерируется 7 машинных инструкций

DWORD dw = * (__unaligned DWORD *) pvDataBuffer;

IIo если я уберу ключевое слово _unaligned, то получу всего 3 машинные инструкции Как видите, модификатор _unaligned на процессорах Alpha приводт к увеличению числа генерируемых машинных инструкций более чем в 2 раза. Но инструкции, добавляемые компилятором, все равно намного эффективнее, чем перехват процессором попыток доступа к невыровненным данным и исправление таких ошибок операционной системой.

И последнее. Ключевое слово _unaligned на процессорах типа x86 компилятором Vtsual С/С++ не поддерживается. На этих процессорах оно просто не нужно. Но это

означает, что версия компилятора для процессоров x86, встретив в исходном коде ключевое слово _unaligned, сообщит об ошибке Полому, если Вы хотите создать единую базу исходного кода приложения для обеих процессорных платформ, используйте вместо _unahgned макрос UNAUGNED Он определен в файле WmNT.h так

#if defined(_M_MRX000) || defined(_M_ALPHA) || defined(_M_IA64)

#define UNALIGNED _unaligned

#if defined(_WIN64)

#define UNALIGNED64 __unaligned

#else

#define UNALIGNED64

#endif

#else

#define UNALIGNED

#define UNALIGNED64

#endif


Содержание раздела