﻿# Урок 26\. Оптимизация 64\-битных программ

## Снижение объема расходуемой памяти

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

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

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

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

Одним из направления уменьшений расходуемой памяти является оптимизация структур данных, о чем подробно было рассказано в уроке 23\.

Еще одним направлением экономии памяти является использование более экономных типов данных\. Например, если необходимо хранить большое количество целых чисел, и их значение никогда не превысит UINT\_MAX, то стоит использовать тип unsigned, а не тип size\_t\.

## Использование memsize\-типов в адресной арифметике

Использование типов [ptrdiff\_t](https://pvs-studio.ru/ru/blog/terms/0041/) и [size\_t](https://pvs-studio.ru/ru/blog/terms/0044/) в адресной арифметике помимо повышения надежности кода может дать дополнительный выигрыш в производительности\. Например, использование в качестве индекса типа int, размерность которого отличается от размерности указателя, приводит к тому, что в двоичном коде будут присутствовать дополнительные команды преобразования данных\. Речь идет о 64\-битном коде, в котором размер указателей стал равен 64\-битам, а размер типа int остался 32\-битным\.

Показать короткий пример преимущества size\_t над unsigned не простая задача\. Чтобы быть объективным необходимо использовать оптимизирующие возможности компилятора\. А два варианта оптимизированного кода часто становятся слишком непохожими, чтобы легко было продемонстрировать отличие\. Попытка создать нечто близкое к простому примеру увенчалась успехом только с шестого раза\. И все равно пример не идеален, так как показывает не лишние преобразования типов данных, о которых сказано выше, а то, что компилятор смог построить более эффективный код при использовании типа size\_t\. Рассмотрим код программы, переставляющий элементы массива в обратном порядке:

```cpp
unsigned arraySize;
...
for (unsigned i = 0; i < arraySize / 2; i++)
{
  float value = array[i];
  array[i] = array[arraySize - i - 1];
  array[arraySize - i - 1] = value;
}
```

В примере переменные "arraySize" и "i" имеют тип unsigned\. Этот тип легко можно заменить на size\_t и сравнить небольшой участок ассемблерного кода, показанный на рисунке 1\.

![26_Optimization_ru/image1.png](https://import.viva64.com/docx/blog/26_Optimization_ru/image1.png)

_Рисунок 1 \- Сравнение 64\-битного ассемблерного кода при использовании типов unsigned и size\_t_

Компилятор смог построить более лаконичный код, когда использовал 64\-битные регистры\. Мы не беремся утверждать, что код, созданный при использовании типа unsigned \(текст слева\), будет работать медленнее, чем код с использованием size\_t \(текст справа\)\. Сравнить скорость выполнения кода на современных процессорах крайне сложная задача\. Но из примера видно, что когда компилятор работает с массивами, используя 64\-битные типы, он может строить более короткий и быстрый код\.

Рассмотрим пример, демонстрирующий преимущества использования типов ptrdiff\_t и size\_t с точки зрения производительности\. Для демонстрации возьмем простой алгоритм вычисления минимальной длины пути\. С полным кодом программы можно познакомиться [здесь](https://pvs-studio.com/external-pictures/testspeedexp.zip)\.

Функция FindMinPath32 написана в классическом 32\-битном стиле с использованием типов unsigned\. Функция FindMinPath64 отличается от нее только тем, что в ней все типы unsigned заменены на типы size\_t\. Других отличий нет\! Согласитесь, что это не является сложной модификацией программы\. А теперь сравним скорости выполнения этих двух функций \(таблица 1\)\.

![26_Optimization_ru/image2.png](https://import.viva64.com/docx/blog/26_Optimization_ru/image2.png)

_Таблица 1 \- Время выполнения функций FindMinPath32 и FindMinPath64_

В таблице 1 показано приведенное время относительно скорости выполнения функции FindMinPath32 на 32\-битной системе\. Это сделано для большей наглядности\.

В первой строке время работы функции FindMinPath32 на 32\-битной системе равно 1\. Это связано с тем, что мы взяли время ее работы за единицу измерения\.

Во второй строке мы видим, что время работы функции FindMinPath64 на 32\-битной системе также равно 1\. Это не удивительно, так как на 32\-битной системе тип unsigned совпадает с типом size\_t и никакой разницы между функцией FindMinPath32 и FindMinPath64 нет\. Небольшое отклонение \(1\.002\) говорит только о небольшой погрешности в измерениях\.

В третьей строке мы видим прирост производительности, равный 7%\. Это вполне ожидаемый результат от перекомпиляции кода для 64\-битной системы\.

Наибольший интерес представляет 4 строка\. Прирост производительности составляет 15%\. Это значит, что простое использование типа size\_t вместо unsigned позволяет компилятору построить более эффективный код, работающий еще на 8% быстрее\!

Это простой и наглядный пример, когда использование данных, не равных размеру машинного слова, снижает производительность алгоритма\. Простая замена типов int и unsigned на типы ptrdiff\_t и size\_t может дать существенный прирост производительности\. В первую очередь это относится к использованию этих типов данных для индексации массивов, адресной арифметики и организации циклов\.

_Примечание\. Хотя статический анализатор [PVS\-Studio](https://pvs-studio.ru/ru/pvs-studio/) специально не ориентирован на оптимизацию программ, его использование способствует рефакторингу кода, делающего код более эффективным\. Например, исправляя потенциальные ошибки, связанные с адресной арифметикой, вы будете использовать [memsize\-типы](https://pvs-studio.ru/ru/blog/terms/0030/), что позволит компилятору строить более оптимизированный код\._

## Intrinsic\-функции

Intrinsic\-функции \- это специальные системно\-зависимые функции, выполняющие действия, которые невозможно выполнить на уровне Си/Си\+\+ кода или выполняющие эти действия намного эффективнее\. По сути, они позволяют избавиться от использования inline\-ассемблера, т\.к\. его использование часто нежелательно или невозможно\.

Программы могут использовать intrinsic\-функции для создания более быстрого кода за счет отсутствия накладных расходов на вызов обычного вида функций\. При этом, естественно, размер кода будет чуть\-чуть больше\. В MSDN приводится список функций, которые могут быть заменены их intrinsic\-версией\. Это, например, memcpy, strcmp и другие\.

В компиляторе Microsoft Visual C\+\+ есть специальная опция "/Oi", которая позволяет автоматически заменять вызовы некоторых функций на intrinsic\-аналоги\.

Помимо автоматической замены обычных функций на intrinsic\-варианты, можно явно использовать в коде intrinsic\-функции\. Вот для чего это может быть нужно:

* Встроенный \(inline\) ассемблерный код не поддерживается компилятором Visual C\+\+ в 64\-битном режиме\. Intrinsic\-код поддерживается\.
* Intrinsic\-функции проще использовать, так как они не требуют знания регистров и других подобных низкоуровневых конструкций\.
* Intrinsic\-функции обновляются в компиляторах\. Ассемблерный же код придется обновлять вручную\.
* Встроенный оптимизатор не работает с ассемблерным кодом\.
* Intrinsic\-код легче переносить, чем ассемблерный\.

Использование intrinsic\-функций в автоматическом режиме \(с помощью ключа компилятора\) позволяет получить бесплатно несколько процентов прироста производительности, а "ручное" \- даже больше\. Поэтому использование intrinsic\-функций вполне оправдано\.

Более подробно с применением intrinsic\-функций можно ознакомиться в [блоге команды Visual C\+\+](https://devblogs.microsoft.com/cppblog/new-intrinsic-support-in-visual-studio-2008/)\.

## Выравнивание

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

```cpp
// 16-byte aligned data
__declspec(align(16)) double init_val[2] = {3.14, 3.14};
// SSE2 movapd instruction
_m128d vector_var = __mm_load_pd(init_val);
```

Источники "[Porting and Optimizing Multimedia Codecs for AMD64 architecture on Microsoft Windows](http://download.microsoft.com/download/5/b/5/5b5bec17-ea71-4653-9539-204a672f11cf/mmcodec_amd64.doc)", "[Porting and Optimizing Applications on 64\-bit Windows for AMD64 Architecture](http://download.microsoft.com/download/5/b/5/5b5bec17-ea71-4653-9539-204a672f11cf/AMD64_PortApp.doc)" дают подробный обзор данных вопросов\.

## Другие способы повышения производительности

Подробно с вопросами оптимизации 64\-битных приложений вы сможете познакомиться в документе "[Software Optimization Guide for AMD64 Processors](http://support.amd.com/TechDocs/25112.PDF)"\.

_Авторы курса: Андрей Карпов \([karpov@viva64\.com](mailto:karpov@viva64.com)\), Евгений Рыжков \([evg@viva64\.com](mailto:evg@viva64.com)\)\._

_Правообладателем курса "Уроки разработки 64\-битных приложений на языке Си/Си\+\+" является ООО "Системы программной верификации"\. Компания занимается разработкой программного обеспечения в области анализа исходного кода программ\. [Сайт компании](https://pvs-studio.ru/ru/pvs-studio/)\._