В чем разница между 16- и 32-разрядными программами?
Windows 95 приносит с собой 32-разрядный код: каковы его преимущества?
Несколько недель назад мне позвонил мой старый приятель, который ведет компьютерную колонку в одной из больших американских газет, и задал вопросы, показавшиеся поначалу простыми: "В чем разница между 16- и 32-разрядными программами? Что реально означает термин "32-разрядный код" и как эта разница (если она есть) влияет на скорость исполнения прикладных программ?".
Его вопросы выглядят особенно своевременными в связи с недавним выпуском в свет корпорацией Microsoft системы Windows 95 - 32-разрядного потомка Windows 3.1, - в которой работают как 32-, так и 16-разрядные прикладные программы приложения. Если верить тому, что можно прочитать в коммерческой прессе, то 32-разрядные программы обязаны исполняться в среде Windows 95 лучше и быстрее. Однако многие пользователи, заменившие свои 16-разрядные программы на 32-разрядные, скажут вам, что заметной разницы в быстродействии нет. Итак, чему вы должны поверить: декларациям продавцов программного обеспечения и теоретиков в области внутреннего представления информации, заявляющих о преимуществах 32-разрядного кода, или опыту пользователей, которые говорят, что 32-разрядный Word for Windows работает ничуть не лучше, чем его 16-разрядная версия.
Чтобы видеть все это в перспективе, начнем с рассмотрения того, что такое 32-разрядный код и чем он отличается от 16-разрядного. Далее мы обсудим разницу в скорости исполнения и закончим рассмотрением основных последствий использования (или неиспользования) 32 разрядов для программ, исполняемых в 32-разрядной операционной системе, например OS/2, Windows NT или Windows 95. Если вам тоже интересно, какова может быть разница от введения дополнительных 16 разрядов, вы найдете наше рассмотрение более глубоким, чем просто "проливающим некоторый свет".
Микропроцессор Intel 8088, который использовался в первых IBM PC, был 16-разрядным. Число разрядов процессора обычно определяется числом его линий данных. 16-разрядный процессор имеет 16 линий данных, т. е. он может принимать и передавать данные группами по 16 бит или 2 байт. Процессор 8088 был в этом отношении некоторой аномалией, поскольку содержал только 8 линий данных (по существу это была упрощенная верси процессора 8086, где было 16 линий данных), но его все же рассматривали как 16-разрядный процессор, так как он мог обрабатывать данные внутри себя 16-бит блоками.
Фраза "обрабатывать данные внутри 16-бит блоками" означает, что набор команд процессора 8088 был предназначен для оперирования 16 битами информации, что в те времена представлялось важным. Большинство микропроцессоров, использовавшихся тогда в персональных компьютерах, включая МП 6502 в Apple II и МП 6510 в Commodore 64, были 8-разрядными интегральными схемами, рассчитанными на обработку данных порциями по 8 бит. В 8-разрядном числе можно хранить значения в диапазоне только от 0 до 255, так что, если вы хотите иметь дело с большими числами, ваша прикладная программа, подготовленная для компьютеров Apple и Commodore, должна уметь соединять вместе два или несколько байт, а обрабатывать их по отдельности. Аналогично, 8088 мог вполне эффективно управляться с 16-разрядными целыми числами от 0 до 65 535, но для оперирования большими числами он вынужден был проделывать некоторую дополнительную работу.
Код, подготовленный в наборе команд для процессора
8088, является 16-разрядным. Программа, использующа
16-разрядный код, будет 16-разрядной. Одно из главных
различий между 16- и 32-разрядной программами состоит в
том, насколько эффективно они работают с большими
числами. Предположим, что 16-разрядная программа должна
сложить два 16-бит целых числа, обозначенных
переменными а и b, и сохранить результат в переменной
c. Сформированные компилятором команды дл
микропроцессора могли бы выглядеть примерно так:
mov ax,[a]
add ax,[b]
mov c,[ax]
Первая команда извлекает из памяти переменную а и
помещает ее в 16-разрядный регистр AX микропроцессора.
Вторая команда добавляет величину b к числу в AX, а
третья копирует результат из AX в ячейку памяти, в
которой должна находиться переменная c.
Теперь предположим, что a, b и c - 32-бит
переменные; как 16-разрядная программа могла бы решить
ту же задачу? На этот раз программа немного менее
очевидна:
mov ax,word ptr [a]
add ax,word ptr [b]
mov word ptr [c],ax
mov ax,word ptr [a+2]
adc ax,word ptr [b+2]
mov word ptr [c+2],ax
Не обращайте внимания на фрагменты word, ptr и +2;
они чуть усложняют текст, но ничего не добавляют к
сложности скомпилированного кода. Важно то, что дл
решения задачи теперь требуется шесть команд и шесть
отдельных обращений к памяти вместо трех. Первые три
команды складывают младшие части чисел (младшие 16
разрядов) двух переменных, а следующие три команды
складывают старшие части - с учетом возможного переноса
из предыдущей операции. Таким образом, 32-бит
переменная рассматривается как две 16-бит переменные и
каждая 16-бит половина обрабатывается отдельно.
Процессор 386 был первым микропроцессором корпорации
Intel с 32-разрядным набором команд, причем тот же
самый базовый набор все еще используется в наши дни в
процессорах 486 и Pentium. 32-разрядные прикладные
программы работают с 32-разрядными величинами так же
легко, как 16-разрядные программы - с 16-разрядными
числами. Та же самая программа, которая складывает a и
b, чтобы получить c, где a, b и c - 32-разрядные целые,
после компиляции на 32-разрядной платформе имеет вид:
mov eax,[a]
add eax,[b]
mov [c],eax
Здесь EAX - это 32-разрядная версия регистра AX. Мы
снова вернулись к трем командам, поскольку 32-разрядные
команды позволяют обрабатывать данные блоками по 32
бит.
Из этих примеров ясно, что при оперировании с 32-разрядными величинами 32-разрядный код превосходит 16-разрядный и что он, вероятно, будет исполнятьс быстрее, так как требует меньше команд и меньше обращений к памяти. Менее очевидно то, что 32-разрядный код при работе с 16-бит величинами не лучше, чем 16-разрядный. Чтобы использовать преимущества 32-разрядного кода лучше иметь дело с 32-бит данными.
Способность 32-разрядных программ работать с большими числами более эффективно, чем это делают 16-разрядные, - не единственное их преимущество. Они также могут легко и быстро обрабатывать большие объемы данных. Единовременно 16-разрядная программа может иметь дело с блоком данных, размер которого не превышает 64 Кбайт, тогда как 32-разрядная программа допускает размер блока данных до 4 Гбайт. Объяснение этого связано с механизмом адресации памяти.
В процессорах корпорации Intel при чтении и записи в память используется схема адресации сегмент:смещение. Сегмент можно рассматривать как окно в память. И хот процессор 8088 имеет адресное пространство в 1 Мбайт, единовременно он может обращаться только к 64 Кбайт. В специальный регистр, называемый сегментным, загружаетс величина, указывающая начальный (кратный 16 байт) адрес сегмента, который располагается в любом месте памяти в пределах 1 Мбайт адресного пространства. 16-разрядный регистр смещения, содержащий величину приращени относительно начального адреса сегмента, задает конкретную ячейку памяти, как показано на рис. 1. Предельный размер сегмента, равный 64 Кбайт, обусловлен тем, что 16-разрядное смещение соответствует не более чем 65 536 (64 Кбайт) разных значениям, так что смещение не может превышать 64 Кбайт минус 1 байт.
Сегментная адресация используется и в 32-разрядных процессорах Intel, но, поскольку 32-бит число может представлять величины от 0 до 4<~>294<~>967<~>295, максимальный размер сегмента составляет 4 Гбайт. Поэтому регистр сегмента можно установить указывающим на нижнюю границу памяти и любой байт в пределах 4-Гбайт адресного пространства процессора при 32-бит смещении оказывается доступным. Программисты называют это сплошным, или неструктурированным, или плоским (fleat) адресным пространством. Такой способ адресации (называемый еще прямой адресацией) применяется в Windows 95, OS/2 и Windows NT.
Теперь зададим важный вопрос: почему так существенно
иметь сплошное адресное пространство? Если прикладна
программа работает с массивами, структурами данных и
другими объектами, размер которых меньше 64 Кбайт, это
не играет особой роли. Но когда размер блока данных
превышает 64 Кбайт, сплошное адресное пространство дает
большой выигрыш, так как программе не приходитс
непрерывно загружать и перезагружать регистры
сегментов, чтобы получить доступ ко всему массиву
данных. Приведем простой пример, иллюстрирующий эту
разницу. 32-разрядная прикладная программа,
использующая сплошное адресное пространство, может
копировать 256 Кбайт данных из одного места в памяти в
другое с помощью всего пяти команд:
cld
mov esi,source
mov edi,destination
mov ecx,10000h
rep movsd
16-разрядная программа, даже если ее код сильно
оптимизирован, для выполнения той же работы требует
примерно в три раза больше команд:
cld
mov cx,4
mov si,0
mov di,0
start:
push cx
mov cx,8000h
rep movsw
mov ax,ds
add ax,1000h
mov ds,ax
mov ax,es
add ax,1000h
mov es,ax
pop cx
loop start
16-разрядная версия этой программы фактически
выполняет 48 команд, так как все команды после строки с
меткой start: исполняются четыре раза. (В этом примере
предполагается, что процессор работает в реальном
режиме. Если же процессор работает в защищенном режиме,
то нужно не просто давать последовательные приращени
регистрам сегментов, а загружать в них форматированные
специальным образом значения селекторов, взятые из
таблицы дескрипторов.) В этом примере 32-разрядна
версия не будет работать в 10 раз быстрее, чем
16-разрядная, поскольку сказывается лимитирующий фактор
в виде времени доступа к памяти. Однако вдвое быстрее
она работать будет - главным образом, вследствие того,
что копирует данные по 4 байт сразу, тогда как
16-разрядная версия копирует за раз только 2 байт, и,
кроме того, в 32-разрядной версии не требуетс
манипулировать с регистром сегментов.
Рис. 1. Сравнение сегментной и прямой адресации памяти
________
4 Гбайт | |
| |
|--------| <- Смещение
1 Мбайт ________ | | (32-бит)
| | | |
| | | |
-|--------|- --- | |
|| || | | |
- Смещение -> ||--------|| | | |
| (16-бит) || || | | |
| || || | | |
- Сегмент -> -|--------|- --- | |
| | 64 Кбайт | |
| | | |
| | | |
| | | |
0 |________| 0 |________| <- Сегмент
Сегментная Пряма
Одно из преимуществ 32-разрядных прикладных
программ перед соответствующими 16-разрядными
заключается в простоте доступа к большим блокам
данных. 16-разрядные программы обращаются к памяти
сегментами, не превышающими 64 Кбайт, а это
означает, что для считывания и записи больших блоков
данных приходится проделывать дополнительную работу.
В то же время 32-разрядные программы используют
сплошное адресное пространство, в котором доступ к
любой ячейке памяти с адресом от 0 до 4 Гбайт
происходит за один шаг.
В статье "The Case for 32 Bits", опубликованной в журнале Microsoft Systems Journal за июль/август 1992 г., внештатный редактор журнала PC Magazine Чарлз Петцольд представил блестящий анализ различий в производительности 16- и 32-разрядных кодов. Дл тестирования он использовал две простые программы сортировки, составленные на Си. В них применялс популярный алгоритм пузырьковой сортировки для массива 32-разрядных целых чисел. В одной программе размер массива был немного меньше 64 Кбайт; в другой - массив был увеличен и занимал немного больше 64 Кбайт. Обе версии - 16- и 32-разрядная - каждой программы были скомпилированы и подвергнуты тестированию.
Результаты вовсе не были неожиданными. 32-разрядна версия программы работала с массивом размером менее 64 Кбайт в 1,4 раза быстрее, чем 16-разрядная (62 и 86 с соответственно), - в основном из-за большей эффективности 32-разрядного кода при операциях с 32-разрядными целыми числами. Однако при размере массива свыше 64 Кбайт 32-разрядная версия стала исполняться в целых пять раз быстрее 16-разрядной - 72 и 366 с соответственно. Ясно, что 32-разрядная верси оперирует с 32-разрядными числами и большими массивами лучше, чем 16-разрядная, которой просто приходитс выполнять больше действий для решения той же задачи.
Значит ли это, что 32-разрядный текстовый процессор будет работать в пять раз быстрее, чем 16-разрядный? Совсем нет. Большая часть времени текстового процессора тратится на ожидание ввода данных от пользователя, а не на выполнение интенсивных вычислений. Кроме того, данные могут быть в основном 8- или 16-бит, а не 32-бит. Прокрутка вероятно станет быстрее, но значительная доля работы при прокрутке страницы осуществляется ускорителем на видеоплате, а не прикладной программой. Большая электронная таблица, вероятно, будет пересчитываться быстрее, но и тут следует учесть ряд других факторов. Степень, до которой 32-разрядный код ускоряет выполнение прикладной программы, зависит от того, в каких объемах в этой программе производятся перемещение данных в памяти, сложение, вычитание, умножение, деление, сравнение и другие виды манипуляций с 32-разрядными величинами, а также операции с крупными массивами.
В течение следующих месяцев вы, без сомнения, услышите о некоторых 32-разрядных программах, работающих медленнее, чем аналогичные 16-разрядные. Разумеется, можно подготовить хорошую 16-разрядную программу, которая превзойдет плохую 32-разрядную. Но, принимая во внимание размер типичной сегодняшней прикладной программы, есть основания полагать, что хорошо составленная 32-разрядная программа почти всегда окажется лучше 16-разрядной реализации той же задачи.
Строго говоря, утверждение, что прикладная программа является 32-разрядной, означает, что в ней используютс 32-разрядные команды и что она обладает доступом к большему адресному пространству. Однако в Windows термин "32-разрядный" имеет более широкое значение. 32-разрядная Windows-программа использует 32-разрядный код, но, кроме того, для нее предусмотрена специальна обработка операционной системой. Ниже приведены лишь некоторые различия между 16- и 32-разрядными Windows-программами.
Если вы раздумываете, стоит ли переходить на 32-разрядные программы, имейте в виду, что со временем 16 разрядов уйдут в небытие, а 32 разряда станут нормой. По существу это вопрос о том, у какого конца линии развития вы хотите быть. Кроме того, к тому моменту, когда вы закончите замену своих 16-разрядных программ на их 32-разрядные версии, компьютерна индустрия начнет переходить на 64 разряда.
Готов биться об заклад, что вы не станете этого дожидаться.