Журнал Компьютерра - Журнал «Компьютерра» №41 от 08 ноября 2005 года
Что вообще такое «разрядность процессора»? Как ни странно, это отнюдь не максимальный размер обрабатываемых данных. Складывать и вычитать 64-битные числа x86-процессоры умеют еще со времен Intel Pentium MMX; более того - даже i486 мог работать не только с 64-битными, но и с 80-битными числами, записанными в формате длинной двойной точности с плавающей запятой (long double). И если уж проводить аналогию дальше, то обрабатываемые инструкциями SSE-наборов операнды (регистры XMM) вообще имеют длину 128 бит. Но поддержка инструкций MMX, x87 и SSE 1/2/3 отнюдь не делает процессор 64-, 80- или 128-битным. Грубо говоря, по возможностям вычислений 64-битный процессор теоретически почти ничем не отличается от 32-битного, но достаточно продвинутого собрата[На самом деле, 32-разрядные процессоры, например, не умеют перемножать целочисленные 64-битные числа и делить 128-битные целые числа на 64-битное число, но это уже детали]. Да, работать с ним не так удобно, но при желании можно. В любом случае, соответствующие данные (long long integer или __int64, в общепринятой терминологии языка C) в программах встречаются нечасто.
Так в чем же дело?
А в том, что и x87, и SSE - расширенные наборы инструкций, работающие со специализированными регистрами процессора. Они никак не затрагивают сердце процессора - его базовый набор инструкций (Instruction Set Architecture, ISA) и базовые регистры общего назначения (General Purpose Registers, GPR), равно как и некоторые «представления» процессора об окружающем его мире. Лирик, наверное, не упустил бы здесь возможности немного пофилософствовать на тему подобных «неощутимых» с первого взгляда, но очень глубоких по своей сути различий, но я не философ, а математик, и потому просто скажу, что на практике главное отличие GPR-регистров от всех остальных в том, что их можно использовать для адресации оперативной памяти. То есть 64-битный процессор - это не тот, который в принципе может работать с 64-битными числами (хотя это он тоже должен уметь делать, выполняя с 64-битными целыми числами все базовые арифметические операции), а тот, который способен этими числами «нумеровать» ячейки памяти.
Чтобы было понятнее, о чем идет речь, поясняю: стандартная модель записи целочисленных чисел позволяет записать в 32-разрядный GPR-регистр процессора архитектуры IA-32 любое целое число от 0 до 232-1. Оперативная память с точки зрения прикладных приложений представляется здесь в виде эдакой длинной ленты из ячеек определенного размера (1 байт в x86), причем все они «пронумерованы» - ячейка 0, ячейка 1 и так далее, вплоть до ячейки 4.294.967.295. Какие-то ячейки могут «отсутствовать» - в этом случае обращение к ним будет вызывать ошибку, однако потенциальная возможность обратиться к этой ячейке существует всегда. А вот у 32-разрядного процессора возможности обратиться к ячейке 4.294.967.295 нет в принципе - просто потому, что он не сможет «дать ей название».
Что это означает на практике? Только то, что ни одна «классическая» 32-битная программа не может использовать больше 4 Гбайт (232/210*210*210) памяти. Поэтому если вы спросите продавца о преимуществах 64-разрядного процессора, то именно эту сакральную фразу о «поддержке большого объема оперативной памяти» (вместе с вопросом «а оно вам надо?») и услышите. Но все-таки не спешите сводить эту возможность к установке в систему четырех двухгигабайтных планок SDRAM. Все далеко не так просто.
64-разрядность и оперативная память
Первая «особенность», о которой часто забывают, рассматривая 64-разрядные процессоры, - это то, что во всех современных компьютерах программы работают не с физической, а с виртуальной оперативной памятью, то есть программная адресация памяти может не совпадать с действительным расположением этой памяти в компьютере. В нашей модели длинной ленты ячеек мы можем нумеровать их в произвольном порядке (0, 6533, 21, 554, 54223563, 2, …). Это очень удобно в многозадачных операционных системах: по-разному пронумеровав ячейки и раздав их разным приложениям (после чего в памяти образуется каша, когда данные разных программ лежат вперемешку), на логическом уровне мы сохраняем линейность и стройность, поскольку каждая программа работает не с этой кашей, а с виртуальным пространством адресов, в которое попадают данные только этой программы, и расположенные именно так, как программе привычно.
Впрочем, перенумерацией дело не ограничивается, поскольку вместе с номерами указываются всяческие атрибуты - «только для чтения», «только для операционной системы» и пр. Вдобавок для некоторых ячеек можно указать, что они в оперативную память «пока не загружены». Встретив такую пометку, CPU генерирует специальную системную ошибку (Page Fault) и обращается за помощью к операционной системе, которая может, к примеру, загрузить данные для этой ячейки с жесткого диска (техника своппинга). Поэтому всегда следует помнить, что «ограничение 4 Гбайт» в первую очередь относится не к физической, а к виртуальной оперативной памяти, доступной процессу. А вот что стоит за этими четырьмя гигабайтами адресного пространства - скромные ли 128 Мбайт памяти SDRAM или кусочек от пары терабайтов дискового массива - неважно: собственно процессор этот «реальный мир» почти ничем не ограничивает.
Но все же - что дает разрядность? Если процессор поддерживает длинные физические адреса (в архитектуре x86 соответствующий режим называется Physical Address Extension, PAE[Если подробнее, то в PAE используется 52-битная адресация, позволяющая адресовать до четырех петабайт данных]), то мы можем поставить на сервер хоть 64 Гбайт оперативной памяти и выделять ее разным приложениям. Условия вроде «не более двух гигов для чипсета такого-то» - это ограничения именно чипсета, не умеющего обслуживать большой объем памяти, а не 32-разрядной системы. Поэтому в серверных приложениях, когда на одном сервере, как правило, запущено несколько десятков одновременно работающих приложений, пресловутый «барьер в 4 Гбайт» практически не ощущается. Но означает ли это, что сегодня 64-разрядный x86 никому не нужен?
Конечно, нет! 64-разрядность виртуальной памяти в приложениях востребована уже сейчас, просто эти «добавочные биты» используются другими способами.
Во-первых, какую-то часть виртуального адресного пространства может забирать операционная система. К примеру, MS Windows обычно использует для своих нужд старший бит виртуального адреса, ограничивая адресное пространство запущенного в ней приложения 31 битом и 2 Гбайт адресного пространства. А два гигабайта оперативной памяти, согласитесь, куда ближе к реальной жизни, нежели гипотетические четыре; и ситуацию, когда их начнет не хватать, представить гораздо проще. Можно, правда, попытаться использовать специальный режим, когда Windows будет предоставлять процессам не два, а три гига адресного пространства, но работает он, к сожалению, далеко не всегда, зачастую роняя при неправильной настройке операционную систему.
Во-вторых, линейное адресное пространство подвержено «засорению» - процессу, в ходе которого в нем образуются дырки, которые невозможно использовать. Программы часто оперируют не байтами и словами, а гораздо более крупными структурами, занимающими десятки, сотни и даже миллионы байт информации. Если мы использовали эту структуру, а затем необходимость в ней отпала, то в памяти остается дыра, совпадающая по размерам и расположению с местом, где лежали данные этой структуры. И удастся ли сию дыру использовать - еще бабушка надвое сказала. Иногда почти вся оперативная память состоит из дырок, не несущих никакой практической пользы, и места для записи даже небольшого объема данных не находится[Подобным особенно грешат операционные системы и среды разработки компании Microsoft: менеджер памяти, используемый в Windows, не столь эффективен, как его UNIX-собратья, и «мусора» в памяти оставляет гораздо больше. Иногда это приводит к тому, что нормально работающие на UNIX программы, использующие вдобавок сравнительно небольшой объем оперативной памяти, в MS Windows через некоторое время вылетают с ошибкой Out of memory].
В-третьих, существует такая интересная техника, как маппинг файлов на оперативную память. «Маппить» можно что угодно, причем это не только самый быстрый, но и один из самых удобных способов обработки файлов. Не нужно ничего читать из файла или записывать в него, не нужно думать об эффективном кэшировании данных - все происходит автоматически. Просто «мапнул» файл на память - и находившиеся в нем данные волшебным образом мгновенно оказались доступными приложению. В «настоящую» оперативную память они будут подгружаться только при обращении к ним, а в случае длительной «невостребованности» - вновь возвращаться на жесткий диск, освобождая место для чего-нибудь более актуального. Реализовать что-либо подобное «вручную» - практически невозможно. Но, к сожалению, на двух гигабайтах адресного пространства Windows особенно не развернешься[И на 4 Гбайт своп-файла, которыми нас обычно ограничивает Windows (ругать так ругать!), - зачастую тоже], поэтому техника маппинга задействуется только тогда, когда высокая скорость обработки файлов для приложения становится критичной.