#опытным
Компилятор конечно умный и сам умеет разворачивать циклы. Но делает он это не во всех случаях. Когда-то уровень оптимизации не позволяет(агрессивная развертка делается на O3) или количество итераций цикла неизвестно.
Если же вы хотите вручную контролировать развертку, но не писать код развертки руками - для вас есть один вариант. Это
#pragma unroll.Правда, есть загвоздка. Стандартного синтаксиса этой директивы не существует, для каждого компилятора чуть свой синтаксис:
-👉🏿GCC и Clang: Используется
#pragma GCC unroll Для Clang существует более детализированный синтаксис #pragma clang loop unroll.👉🏿 MSVC (Microsoft Visual C++): здесь все, как не у всех: в msvc нет прямого аналога директивы
#pragma unroll. Есть #pragma loop, но она контролирует скорее автовекторизацию, чем развертку.👉🏿 Intel и NVIDIA компиляторы: Используют
#pragma unroll.👉🏿 ARM компилятор: Поддерживает
#pragma unroll.Раз у всех все свое, то в примерах будет использовать некую усредненную версию.
По сути есть 2 возможных варианта использования директивы:
🙈 Полная развертка
#pragma unroll
for (int i = 0; i < 12; ++i) {
p1[i] += p2[i] * 2;
}
В этом случае компилятор обязан знать число итераций цикла, чтобы полностью его развернуть.
Плюс чем больше итераций, тем сильнее распухает код бинаря.
Поэтому в данном виде директива встречается не так часто.
☃️ Частичная развертка
Это именно та история, которую мы обсуждаем всю серию статей.
#pragma unroll N позволяет развернуть цикл так, чтобы за одну итерацию тело цикла выполнялось N раз.#pragma GCC unroll 4
for (size_t i = 0; i < n; ++i) {
sum += arr[i];
}
Как прагма работает?
1️⃣ Препроцессор оставляет
#pragma unroll после препроцессинга в единице трансляции для того, чтобы компилятор смог ее учесть.2️⃣ На этапе семантического анализа компилятор встречает
#pragma unroll и связывает его со следующим за ним циклом.3️⃣ Специальная сущность в компиляторе, отвечающая за развертку циклов, слушает и повинуется прагме и делает столько операций в цикле, сколько сказал разработчик. Плюс добавляет обработку хвоста.
Здесь можно посмотреть примерчики с gcc-шной версией прагмы.
Напомню, что компилятор сам хорошо умеет оптимизировать циклы, особенно на O3. Используйте прагму только тогда, когда вы сами ручками проверили, что компилятор в вашем случае бессилен или делает что-то неправильно.
Плюс обязательно делайте перфоманс тестирование, чтобы выявить действительно лучший по производительности вариант.
Measure your performance. Stay cool.
#compiler #optimization #performance