В форумах люди часто упоминают, что 64-битные версии программ поглощают больший объем памяти и стека. При этом обычно ссылаются на то, что размеры данных стали в 2 раза больше. Однако это необоснованное утверждение, так как размер большинства типов (char, short, int, float) в языке Си/Си++ остался прежним на 64-битных системах. Конечно, например, увеличился размер указателей, но ведь не все данные в программе состоят из указателей. Причины роста потребляемой памяти и стека более сложны. Я решил подробнее исследовать данный вопрос.

В данной заметке я поговорю о стеке, а в будущем планирую обсудить выделение памяти и размер двоичного кода. И еще хочу сразу заметить, что статья посвящена языку Си/Си++ и среде разработки Visual Studio.

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

При разработке соглашений по вызовам (calling conventions) для архитектуры x86-64 решили положить конец существованию различных вариантов вызова функций. В Win32 существовал целый ряд соглашений о вызове: stdcall, cdecl, fastcall, thiscall и так далее. В Win64 только одно «родное» соглашение по вызовам. Модификаторы подобные __cdecl компилятором игнорируются. Думаю, что все согласятся в благородстве такого резкого сокращение числа соглашений.

Соглашение по вызовам на платформе x86-64 похоже на соглашение fastcall, существующее в x86. В x64-соглашении первые четыре целочисленных аргумента (слева направо) передаются в 64-битных регистрах, выбранных специально для этой цели:

RCX: 1-й целочисленный аргумент
RDX: 2-й целочисленный аргумент
R8: 3-й целочисленный аргумент
R9: 4-й целочисленный аргумент

Остальные целочисленные аргументы передаются через стек. Указатель «this» считается целочисленным аргументом, поэтому он всегда помещается в регистр RCX. Если передаются значения с плавающей точкой, то первые четыре из них передаются в регистрах XMM0-XMM3, а последующие - через стек.

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

Хотя аргументы могут быть переданы в регистрах, компилятор все равно резервирует для них место в стеке, уменьшая значение регистра RSP (указателя стека). Как минимум, каждая функция должна резервировать в стеке 32 байта (четыре 64-битных значения, соответствующие регистрам RCX, RDX, R8, R9). Это пространство в стеке позволяет легко сохранить содержимое переданных в функцию регистров в стеке. От вызываемой функции не требуется сбрасывать в стек входные параметры, переданные через регистры, но резервирование места в стеке при необходимости позволяет это сделать. Если передается более четырех целочисленных параметров, в стеке нужно зарезервировать соответствующее дополнительное пространство.

Рассмотрим пример. Некая функция передает два целочисленных параметра дочерней функции. Компилятор положит значения аргументов в регистры RCX и RDX и при этом вычтет 32 байта из регистра RSP. Вызываемая функция может обратиться к параметрам через регистры RCX и RDX. Если же коду этой функции данные регистры понадобятся для какой-то иной цели, он сможет скопировать их содержимое в зарезервированное пространство стека размером 32 байта.

Описанная особенность приводит к существенному возрастанию скорости поглощения стека. Даже если функция не имеет параметров, то от стека все равно будет «откушено» 32 байта, которые затем никак не используются. Смысл использования такого неэкономного механизма я не уловил. Что-то говорится про унификацию и упрощение отладки, но как-то расплывчато.

Обратим внимание еще на один момент. Указатель стека RSP должен перед очередным вызовом функции быть выровнен по границе 16 байт. Таким образом, суммарный размер используемого стека при вызове в 64-битном коде функции без параметров составляет: 8 (адрес возврата) + 8 (выравнивание) + 32 (резерв для аргументов) = 48 байт!

Рассмотрим, к чему это может приводить на практике. Здесь и далее для экспериментов я буду использовать Visual Studio 2010. Составим рекурсивную функцию вида:

Void StackUse(size_t *depth) { volatile size_t *ptr = 0; if (depth != NULL) ptr = depth; cout << *ptr << endl; (*ptr)++; StackUse(depth); (*ptr)--; }
Функция немного запутанна, чтобы оптимизатор не превратил ее в «ничто». Основное здесь следующее: функция имеет аргумент типа указатель и одну локальную переменную, также типа указатель. Посмотрим, сколько стека потребляет функция в 32-битном и 64-битном варианте и сколько раз она может быть рекурсивно вызвана при стеке размером 1 мегабайт (размер по умолчанию).

Release 32-bit: последнее выведенное число (глубина стека) - 51331
Компилятор использует при вызове данной функции 20 байт.

Release 64-bit: последнее выведенное число - 21288
Компилятор использует при вызове данной функции 48 байт.

Таким образом, 64-битный вариант функции StackUse оказывается прожорливее 32-битного в более чем в 2 раза.

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

Struct S { char a; size_t b; char c; }; void StackUse(S s) { ... }
Размер структуры "S" из-за изменений правил выравнивания и изменения размера члена "b" вырастет с 12 до 24 байт при перекомпиляции в 64-битном режиме. Структура передается в функцию по значению. А, следовательно, структура в стеке также займет в два раза больше памяти.

Неужели все так плохо? Нет. Не следует забывать про большее количество регистров имеющихся в распоряжении 64-битного компилятора. Усложним код экспериментальной функции:

Void StackUse(size_t *depth, char a, int b) { volatile size_t *ptr = 0; int c = 1; int d = -1; for (int i = 0; i < b; i++) for (char j = 0; j < a; j++) for (char k = 0; k < 5; k++) if (*depth > 10 && k > 2) { c += j * k - i; d -= (i - j) * c; } if (depth != NULL) ptr = depth; cout << c << " " << d << " " << *ptr << endl; (*ptr)++; StackUse(depth, a, b); (*ptr)--; }
Результаты запуска:

Release 32-bit: последнее выведенное число - 16060
Компилятор использует при вызове данной функции уже 64 байта.

Release 64-bit: последнее выведенное число - 21310
Компилятор использует при вызове данной функции по-прежнему 48 байт.

Для данного примера 64-битному компилятору удалось использовать дополнительные регистры и построить более эффективный код, что позволило сократить количество используемой стековой памяти!

Выводы

  1. Невозможно предсказать, сколько стековой памяти будет использовать 64-битный вариант программы по сравнению с 32-битным. Размер может быть как меньше (что маловероятно), так и значительно больше.
  2. Для 64-битной программы на всякий случай стоит увеличить объем зарезервированного стека в 2-3 раза. Лучше в 3 раза для спокойствия. Для этого в настройках проекта имеется параметр Stack Reserve Size (ключ /STACK:reserve). По умолчанию размер стека составляет 1 мегабайт.
  3. Не следует беспокоиться, что 64-битная программа потребляет больше стековой памяти. Физической памяти в 64-битных системах значительно больше. Стек размером 2 мегабайта на 64-битной системе с 8 гигабайтами памяти, занимает меньший процент памяти, чем 1 мегабайт стека в 32-битной системе с 2 гигабайтами памяти.

Wow64 это сокращение от «Windows on Windows64». Так обозначается эмулятор 32-битного, x86-совместимого окружения, позволяющего запускать 32-битные приложения на 64-битных версиях Windows. Соответствующий раздел в MSDN (http://windowssdk.msdn.microsoft.com/en-us/library/ms775191.aspx) хорошо описывает отличия между окружением 32-битных и 64-битных приложений. Я же хотел бы рассказать о нескольких, очевидных и не очень, особенностях взаимодействия 32 и 64-битных приложений.

Wow64 перехватывает все переходы между 32-битным кодом приложения и ядром системы, что включает в себя как вызовы сервисов ядра, так и обратные вызовы 32-битного кода из ядра. Для этого 64-битные версии Windows включают в себя специальные версии 32-битных Ntdll.dll, User32.dll и Gdi32.dll, которые передают управление Wow64 вместо обычного системного вызова. В свою очередь Wow64 переключается в 64-битный режим, преобразует переданные 32-битные указатели в 64-битные и выполняет системный вызов. По окончанию вызова, Wow64 выполняет обратное преобразование возвращаемых значений. Также перехватываются исключения и обратные вызовы (callbacks). Таким образом, 32-битное приложение полностью изолируется от системы и других 64-битных приложений.Одним из следствий подобной изоляции является запрет на смешивания 32-битного и 64-битного кода в рамках одного процесса. 64-битный код может вызывать системные сервисы, минуя Wow64, что, в свою очередь, может нарушить целостность внутренних структур Wow64. С очевидными последствиями.

Наиболее неприятное следствие этого запрета – невозможность использования 32-битных in-process COM объектов 64-битными процессами. Это может стать серьезной проблемой при переносе 32-битного приложения в 64-битное окружение в случае, если исходные коды используемых in-process COM компонентов недоступны.

32-битные и 64-битные процессы по-прежнему могут использовать все существующие средства межпроцессного взаимодействия. Wow64 берет на себя все заботы по преобразованию указателей.

Как я уже писал ранее, Wow64 включает в себя файловый редиректор (file system redirector), который скрывает от 32-битных программ каталог «%windir%\System32», переправляя все запросы в «%windir%\SysWow64». В этом каталоге находятся 32-битные версии 64-разрядных компонентов из «%windir%\System32». Фактически «%windir%\SysWow64» это копия «%windir%\System32» 32-битной версии Windows.

Подобный дизайн обладает неожиданным побочным эффектом – несмотря на то, что система не накладывает ограничений на запуск 64-битных приложений из 32-битных, 32-битные приложения не имеют возможности напрямую запустить приложение установленное в «%windir%\System32». Так, например, открыв окно 32-битного «cmd.exe» нельзя так просто переключиться в 64-битный «cmd.exe». Конечно же, существует способы бороться с этой проблемой, однако все они опираются на использование специфичных для 64-битной системы механизмов, о существовании которых большинство 32-битных приложений не подозревает.

И последнее ограничение на сегодня - 64-битные версии Windows требуют, чтобы все драйвера в системе были 64-битными. На первый взгляд это не должно быть проблемой, однако это касается и драйверов принтеров, что автоматически затрагивает большинство программ PDF-генераторов. 32-битный драйвер виртуального принтера, который используется для генерации PDF, не работает на 64-битной системе. Точно также не работают старые версии эмуляторов различных устройств и некоторые программы для записи CD/DVD.

Резюме. 32-битные приложения на удивление хорошо интегрируются в 64-битную систему. Особенно если учесть, что 32 и 64-битный бинарный код не совместим даже на Amd64, не говоря уже об Itanium (IA-64). Тем не менее, обеспечение совместимости добавит (и уже добавило) головной боли разработчикам.

Windows 7

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

Windows XP Mode дает возможность запускать приложения Windows XP, есть поддержка работы системы на 35 языках. Пользовательские данные надежно защищены новыми инструментами BitLocker To Go, BitLocker, что предотвращает несанкционированный доступ к персональной информации. Windows 7 64 bit Максимальная х64 поддерживает современные 64-х битные процессоры, это обеспечивает большую мощность при работе.

Особенности продукта:

  • работа с 64-х разрядными системами;
  • удобная панель задач, значки которой можно переносить на любое место;
  • имеется возможность предпросмотра окон; установленный Internet Explorer 8;
  • поиск файлов стал еще удобнее;
  • запуск приложений стал более быстрым, удобным для пользователя;
  • гаджеты теперь можно размещать в любом удобном месте рабочего стола;
  • имеется функция родительского контроля;
  • установлены такие функции, как Aero Shake, Aero Peek, Snap; доступен обозреватель игр, возможности которого расширены, имеются также многопользовательские игры (нарды, пики, шашки и прочее);
  • для комфортного просмотра фото, ТВ, развлечений, прослушивания музыки установлен Media Center; для создания, безопасного использования домашней сети имеется «Домашняя группа»;
  • Anytime Upgrade – обновление без установки дополнительной установки;
  • данные можно быстр восстанавливать, архивировать;
  • можно запускать старые приложения версии ОС Windows XP;
  • ПК можно использовать в офисе при помощи удобной функции «Присоединение к домену»;
  • интерфейс доступен на 35 языках;
  • BitLocker делает шифрование данных полностью безопасным.

Скачать Windows 7 Максимальная 64 bit можно с официальной страницы. Такая система упрощает выполнение любых стандартных задач, в том числе, организацию баз данных, персональные настройки, просмотр файлов, сетевые соединения. Совершенно новые навигационные функции Jump Lists, Aero Shake, Snap повышают комфорт работы в системе, гарантируют абсолютно безопасный доступ к USB-носителям, рабочим станциям.

Windows Media Center обеспечивает удобную работу с домашним медиацентром, проигрывание файлов, потокового видео. Теперь пользователям доступны новые возможности не только в прослушивании музыкальных файлов, но и в просмотре фотографий, проигрывании дисков. Приложение позволяет просматривать и записывать любимые передачи, получить доступ к новейшим развлечениям в сети Интернет. Windows 7 Ultimate обладает улучшениями, ускоряющими работу, запуск/завершение работы, использование ждущего режима, облегчающими работу с устройствами USB. Кроме того, пользовательские настройки стали более простыми, при этом возможности их расширены.

Windows 7 имеет функцию экономии, что продлевает длительность работы ПК от батареи.

Обновление до Windows 7 SP1

Обновите бесплатно вашу Windows 7 до новой версии Windows 7 Service Pack 1. Размер загружаемых файлов 538 Мб для 32-й версии и 903 Мб для 64-й версии.

В статье рассмотрен ряд способов повышения производительности 64-битных Windows приложений.

Введение

1. Эффект от перехода на 64-битные системы

2. Оптимизация программного кода

3. Снижение расхода памяти

Заключение

Библиографический список

Введение

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

1. Эффект от перехода на 64-битные системы

В 64-битной среде старые 32-битные приложения выполняются благодаря подсистеме Wow64. Эта подсистема эмулирует 32-битное окружение, за счет дополнительной прослойки между 32-битным приложением и 64-битным Windows API. Где-то это прослойка тонкая, где-то не очень. Для средней программы потери в производительности из-за наличия такой прослойки составят около 2%. Для некоторых программ это значение может быть больше. Конечно 2% это не много, но все-таки следует учитывать, что 32-битные приложения работают немного медленнее под управлением 64-битной операционной системы Windows, чем под 32-битной.

Компиляция 64-битного кода не только исключает необходимость в Wow64, но и дает дополнительный прирост производительности. Это связано с архитектурными изменениями в микропроцессоре, такими как увеличение количества регистров общего назначения. Для средней программы прирост производительности, от простой перекомпиляции программы можно ожидать в приделах 5-15%. Но здесь все опять очень сильно зависит от типа приложения и типа данных, которые она обрабатывает. Например, компания Adobe заявляет, что новый 64-битный "Photoshop CS4" на 12% быстрее его 32-битной версии.

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

В качестве примера можно привести интеграцию Альфа-Банком в свою IT-инфраструктуру платформы на базе Itanium 2. Рост инвестиционного бизнеса банка привел к тому, что система в имеющейся конфигурации перестала справляться с возрастающей нагрузкой: задержки в обслуживании пользователей временами достигали критической отметки. Анализ ситуации показал, что узким местом системы является не производительность процессоров, а ограничение 32-разрядной архитектуры в части подсистемы памяти, не позволяющей эффективно использовать больше 4 Гбайт адресного пространства сервера. Объем базы данных превышал 9 Гбайт. Использовалась она весьма интенсивно, что приводило к критической загрузке подсистемы ввода-вывода. Альфа-Банк принял решение о закупке кластера из двух четырехпроцессорных серверов на базе Itanium 2 с объемом оперативной памяти - 12 Гбайт. Это решение позволило обеспечить необходимый уровень быстродействия и отказоустойчивости системы. По словам представителей компании, внедрение серверов на основе Itanium 2 позволило ликвидировать проблемы и достигнуть значительной экономии средств .

2. Оптимизация программного кода

Можно говорить об оптимизации на трех уровнях: оптимизации на уровне инструкций микропроцессора, оптимизация кода на уровне языков высокого уровня и алгоритмическая оптимизация с учетом особенностей 64-битных систем. Первая оптимизация доступна при использовании таких средств разработки как ассемблер и слишком специфична, чтобы быть интересной широкому кругу читателей. Для тех, кто интересуется этой тематикой, можно порекомендовать, например, руководство компании AMD по оптимизации приложений для 64-битной архитектуры: "Software Optimization Guide for AMD64 Processors" . Алгоритмическая оптимизация уникальна для каждой задачи и ее обсуждение также выходит за рамки данной статьи.

С точки зрения языков высокого уровня, таких как Си++, оптимизация под 64-битную архитектуру заключается в выборе оптимальных типов данных. Использование однородных 64-битных типов данных позволяет оптимизирующему компилятору строить более простой и эффективный код, так как отсутствует необходимость частого преобразования 32-битных и 64-битных данных между собой. В первую очередь это относится к переменным используемых в качестве счетчиков циклов, индексов массивов и для переменных хранящих различные размеры. По традиции в языках Си и Си++ для представления перечисленных объектов используются такие типы как: int, unsigned и long. В рамках 64-битных Windows систем, использующих модель данных LLP64 , эти типы остались 32-битными. Это приводит в ряде случаев к построению менее эффективного кода за счет дополнительных преобразований. Например, чтобы в 64-битном коде вычислить адрес элемента в массиве, 32-битный индекс должен быть вначале расширен до 64-бит.


Таблица 1. Размерность типов данных в 32-битных и 64-битных версиях операционной системы Windows.

Использование ptrdiff_t, size_t и производных от них типов позволяет оптимизировать программный код от единиц до десятков процентов. Вы можете познакомиться с примером такой оптимизации в статье "Разработка ресурсоемких приложений в среде Visual C++" . Дополнительным преимуществом от использования типов ptrdiff_t и size_t является более надежный код. Использование 64-битных переменных в качестве индексов позволяет избежать переполнений, когда необходимо иметь дело с большими массивами, состоящими из нескольких миллиардов элементов.

Изменение типов данных является непростым занятием, тем более, если производить замену только там, где она действительно необходима. В качестве инструмента, который облегчит этот процесс, можно предложить статический анализатор Viva64. Хотя он специализируется на поиске ошибок в 64 битном коде, следуя его рекомендациям по изменению типов данных, можно существенно увеличить производительность кода.

3. Снижение расхода памяти

После компиляции программы в 64-битном режиме она начинает потреблять большее количество памяти, чем ее 32-битный вариант. Часто это увеличение почти незаметно, но иногда потребление памяти возрастает в 2 раза. Увеличение расхода памяти связано со следующими причинами:

  • увеличение объема памяти для хранения некоторых объектов, например указателей;
  • изменение правил выравнивания данных в структурах;
  • увеличение расхода стековой памяти.

С увеличением потребляемой оперативной памяти часто можно мириться. Преимущество 64-битных систем как раз и заключается в том, что этой памяти много. Нет ничего отрицательного, что на 32-битной системе с 2 гигабайтами памяти программа занимала 300 Мбайт, а на 64-битной системе с 8 гигабайтами памяти программа займет 400 Мбайт. В относительных единицах мы получим, что программа на 64-битной системе занимает в 3 раза меньше доступной физической памяти. С описанным ростом потребляемой памяти нет смысла бороться. Проще добавить еще немного памяти.

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

Предположим, мы имеем программу, которая обрабатывает большой объем текстовых данных (до 400 Мбайт). Для своих нужд программа создает массив указателей, каждый из которых указывает на очередное слово в обрабатываемом тексте. Пусть средняя длина слова равна 5 символов. Тогда программе потребуется около 80 миллионов указателей. Итого, 32-битный вариант программы потребует 400 Мбайт + (80 Мбайт * 4) = 720 Мб памяти. А вот 64-битной версии программы потребуется уже 400 Мбайт + (80 Мбайт * 8) = 1040 Мбайт памяти. Это существенный прирост, который может отрицательно сказаться на производительности программы. Причем, если нет необходимости обрабатывать тексты размером в гигабайты, никакой пользы от выбранной структуры данных нет. Простым и эффективным решением может стать использование вместо указателей, индексов типа unsigned. В этом случае размер потребляемой памяти вновь станет равной 720 Мбайт.

Бесполезно растратить существенный объем памяти можно и в связи с изменением правил выравнивания данных. Рассмотрим пример:

Размер структуры MyStruct2 по прежнему останется равен 12 байт в 32-битной программе, а в 64-битной составит всего 16 байт. При этом с точки зрения эффективности выбоки данных структуры MyStruct1 и MyStruct2 равнозначны. Более наглядно отображение стрктур в память показано на рисунке N1.


Рисунок 1. Представление структур в памяти.

Дать четкие инструкции по оптимизации расположения полей в структурах сложно. Но общая рекомендация такова - следует располагать объекты в порядке уменьшения их размера.

И последнее - рост расхода стековой памяти. Размер увеличивается за счет хранения более длинных адресов возврата и выравнивания. Здесь нет смысла оптимизировать. На стеке разумный программист никогда не будет создавать объекты, размером в мегабайты. Заметим только, что если вы переносите 32-битную программу на 64-битную систему, то не забудьте изменить в настройках проекта размер выделяемого стека. Например, увеличьте его в 2 раза. По умолчанию 64-битному приложению по-прежнему, как и 32-битному, выделяется стек в размере 2 Мбайт. Этого неожиданно может оказаться недостаточным и имеет смысл подстраховаться.

Заключение

Автор надеется, что статья поможет в разработке эффективных 64-битных решений и приглашает для лучшего знакомства с 64-битными технологиями посетить сайт www.viva64.com. На этом сайте вы сможете найти множество ресурсов посвященных разработке, тестированию и оптимизации 64-битных приложений. Успехов вам в ваших 64-битных проектах!

Библиографический список

  1. Валентин Седых. Российские 64 бита: расставим точки над "i".
    http://www.citforum.ru/hardware/arch/64bit_russian/.
  2. Software Optimization Guide for AMD64 Processors.
    http://www.viva64.com/go.php?url=59.
  3. Blog "The Old New Thing": "Why did the Win64 team choose the LLP64 model?"
    http://www.viva64.com/go.php?url=25.
  4. Андрей Карпов, Евгений Рыжков. Разработка ресурсоемких приложений в среде Visual C++.
    http://www.viva64.com/articles/Resource_intensive_applications_%28rus%29.html.