Ричард Гербер, Арт Бик, Кевин Смит, Ксинмин Тиан
Оптимизация ПО. Сборник рецептов
2006, Питер 2010, 2-е издание, выдержки Дятлова Н. С. от ?, 1–?/?=?%
1. Здесь не виден явно другой вариант оптимизации — динамическая очистка цикла.
2. Компилятор C++ производства Intel поддерживает внутренние команды [intrinsics — Н.Д.], напрямую отображающиеся на SIMD-команды (так же как и на многие другие команды ассемблера). Для включения внутренних команд, относящихся к технологии SIMD, просто подключите один их следующих заголовочных файлов: #include <mmintrin.h> // MMX #include <xmmintrin.h> // SSE (нужен также заголовочный файл mmintrin.h) #include <emmintrin.h> // SSE2 (нужен также заголовочный файл xmintrin.h) #include <pmmintrin.h> // SSE3 (нужен также заголовочный файл emintrin.h)
3. Внутренние команды напоминают язык ассемблера, за исключением того, что работу по фактическому выделению регистров, планирование команд и режим адресации они оставляют компилятору. Если не считать явно выровненных внутренних команд загрузки и сохранения, таких как _mm_loadu_si128() и _mm_storeu_si128(), компилятор предполагает, что упакованные операнды внутренних команд должным образом выровнены.
4. Поскольку многие детали оставляются компилятору, пользоваться внутренними командами проще, чем языком ассемблера, но возможностей контролировать генерируемые компилятором команды меньше. Объявление переменной как _m128i xmm0 не влияет на выделение регистров, поскольку имя для компилятора является просто идентификатором.
5. Не думайте, что максимальная производительность требует программирования на ассемблере, поскольку компиляторы часто прибегают к многочисленным вариантам оптимизации, которые вы можете пропустить. Лучше всего выбрать самый простой вариант, обеспечивающий желаемые функциональные возможности, а затем путем анализа производительности определить, что ещё требуется, дополнительная оптимизация или более сложная техника кодирования.
6. Внутренние команды компилятора: похожи на реальные команды ассемблера, встроенные функции задают команды, но не регистры или режимы адресации. [Среди достоинств встроенных команд]: доступ ко всем командам без необходимости заниматься распределением регистров и режимами адресации.
7. [Недостатки автоматической векторизации]: компилятор не всегда способен выделить SIMD-команды из последовательного кода.
8. Так же как и с любыми вариантами оптимизации, усилия по переписыванию кода должны быть сосредоточены на тех частях приложения, которые дадут самый значительный эффект.
9. Главная трудность, с которой вы можете столкнуться при использовании SIMD-команд — это обеспечение благоприятной с точки зрения технологии SIMD организации данных. При проектировании и написании приложения и отдельных его алгоритмов всегда заботьтесь об организации данных таким образом, чтобы добиться максимальной эффективности кэша и дружественности данных для технологии SIMD. Дружественные для SIMD данные выровнены в памяти надлежащим образом и с ними легко работать при помощи SIMD-команд (нет необходимости обращаться к отдельным частям регистра). Горячие в смысле расходования времени точки приложения — это главные кандидаты для вставки SIMD-команд. После выявления горячих точек при помощи какого-либо инструмента, такого как анализатор производительности VTune, необходимо проанализировать каждую найденную точку с целью определить, к каким данным выполняется обращение.
10. Хранимые в памяти операнды SIMD-команд для достижения максимальной производительности должны быть надлежащим образом выровнены. Хранимые в памяти операнды должны быть выровнены, по крайней мере, по 8-байтной границе для технологии MMX и по 16-байтной для потоковых SIMD-расширений. При обработке невыровненных данных SSE-, SSE-2 или SSE3-командами запускается исключение, если только не использовать специальных (и более медленных) команд для перемещения невыровненных данных.
11. Часто можно обнаружить, что компилятор сам оптимизирует кадры стека и структуры данных в смысле выравнивания памяти. Тем не менее, есть три метода, позволяющие программисту влиять на выравнивание памяти.
12. Во-первых, в языках C и C++ добавление перед объявлением конструкции __declspec(align(база, смещение)) означает, что для объявленной сущности предполагается выделить память, начиная с адреса a, который удовлетворяет условию: a mod база = смещение.
13. Например, программист может запросить 64-байтное выравнивание для массива следующим образом: __declspec(align(64)) double a[N];
14. Вы можете легко исправить это нарушение выравнивания, введя в структуру дополнительную 4-байтную переменную dummy, которая никогда не используется.
15. Проблема организации данных очень часто встречается при использовании SIMD-команд.
16. Очевидно, что выполнение четырех умножений может быть хорошим применением для SIMD-команд. Однако возникает одно осложнение — скалярное произведение требует складывать вместе все отдельные элементы регистра. Реализация такой операции, которая называется сквозным суммированием регистра, для большинства упакованных типов данных в виде одной команды не существует. Вы можете произвести сквозное суммирование регистра для чисел с плавающей точкой одинарной точности при помощи двух команд haddps или просто сохранить полученный вектор в памяти и сложить элементы как скаляры.
17. Сквозное суммирование регистра является медленной операцией и может поглотить весь выигрыш от использования SIMD-команд. Хитрость состоит в том, чтобы найти другой способ записывать данные, чтобы не нужно было ни сквозное суммирование регистра, ни применение других операций, которым необходим доступ к отдельным элементам SIMD-регистра. Решение появляется, если делать четыре скалярных произведения вместо одного.
18. Вместо того чтобы хранить данные в восьми отдельных массивах, организованных в виде структуры массивов, вы можете чередовать данные, организованные в массив структур.
19. Надлежащие выравнивания памяти и организация данных являются основными факторами повышения производительности при использовании SIMD-команд.
20. Если же что-то делается неправильно, можно потерять большую часть выигрыша от применения технологии SIMD.
21. Выбор самого «узкого» из допустимых типов данных (который соответствует задаче) обеспечивает максимально возможный параллелизм.
22. Соответствующие SIMD-команды и команды FPU-блока x87 обрабатывают типы данных с плавающей точкой как одинарной, так и двойной точности. Однако с этими типами данных SIMD-команды работают в «родном» формате (32 или 64 бита соответственно), а FPU-Блок x87 расширяет их для выполнения вычислений до формата с плавающей точкой двойной точности (80 бит), а затеем перед записью в паять округляет результат, возвращаясь к формату одинарной или двойной точности. Таким образом, при выполнении одной и той же операции над одними и тем же значениями одинарной или двойной точности FPU-Блок x87 может выдать результат, несколько отличающийся от результата выполнения SIMD-команд с плавающей точкой.
23. Помните о разнице в точности операций с плавающей точкой, которая может иметь место при выполнении команд FPU-блока x87 и SIMD-команд.