CXX95 Telegram 74
#compiler #madskillz

[[assume]] - помоги компилятору сам 😎

Раньше я писал про std::unreachable (он же __builtin_unreachable до C++23) - https://www.tgoop.com/cxx95/58.

Эта штука делает указание компилятору, что в данную ветку исполнения программа никогда не попадет (под личную ответственность программиста), поэтому можно оптимизировать это место.

В C++23 по такому образу стандартизировали похожий функционал: атрибут [[assume(expr)]] (он же __builtin_assume до C++23).

Эта штука делает указание компилятору, что в данной ветке исполнения выражение expr следует считать равным true, и делать разные оптимизации на основе этих данных. Выражение expr вычисляться во время работы программы не будет, это подсказка времени компиляции.

На cppreference (ссылка выше) информации мало, лучше почитать "предложение" о стандартизации: https://wg21.link/p1774r8

Самый простой пример - метод, который делит число на 32:
int div32(int x) {
return x / 32;
}
Казалось бы, очевидная оптимизация - не делить на 32, а сделать битовый сдвиг на 5 битов:
int div32(int x) {
return x >> 5;
}
Но будет неправильно работать на отрицательных числах. Компилятор всегда должен учитывать возможность входа отрицательного числа, из-за этого метод больше по размеру: ссылка на godbolt.

Если программист совершенно точно знает, что все числа будут неотрицательными, то нужно сделать так:
int div32_2(int x) {
[[assume(x >= 0)]]; // или __builtin_assume(x >= 0);
return x / 32;
}
И тогда код оптимизируется: ссылка на godbolt.

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

Некоторые assume можно сделать общими для всего кода (в "предложении" есть пример с умными указателями), но в целом это вещь для узкого круга разработчиков. Есть несколько особенностей этой фичи:

1️⃣ Нужно действительно сильно зависеть от быстродействия программы, например это могут быть реалтаймовые программы. Я однажды кидал видео-выступление Тимура Думлера (автора "предложения") на эту тему - https://www.tgoop.com/cxx95/16.

2️⃣ Нужно понимать, за счет чего срезаются инструкции. Пример программы, которая ограничивает значения массива через std::clamp:
void limiter(float* data, size_t size) {
[[assume(size > 0)]];
[[assume(size % 32 == 0)]];
for (size_t i = 0; i < size; ++i) {
[[assume(std::isfinite(data[i]))]];
data[i] = std::clamp(data[i], -1.0f, 1.0f);
}
}
Предполагая, что размер буфера всегда больше 0 и кратен 32, а флоаты нормализованные, программист ставит assume.
Первый и третий assume не дает делать лишние проверки, а второй assume вероятно как-то связан с кэш-линией процессора.

3️⃣ Нужно постоянно лезть в ассемблер скомпилированной программы и проверять результат - а как иначе? И даже нужно делать юнит-тесты на генерируемый ассемблер (я бы по крайней мере делал). У компиляторов C++ много тестов на получающийся ассемблер, и в отдельных программах с assume они тоже нужны.

4️⃣ Стандарт отмечает, что компиляторы сами вольны оптимизировать код как смогут, никаких требований на них не налагается. Надо проверять, как работает отдельный компилятор и даже отдельная версия, для этого нужны юнит-тесты из 3️⃣ пункта

Можно сделать разные приколы с assume 😁
😱 Фиксируем вариант в switch - ссылка на godbolt.
😱 Решаем простые уравнения с переменной - ссылка на godbolt.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8😁4🤯4🔥2🖕1



tgoop.com/cxx95/74
Create:
Last Update:

#compiler #madskillz

[[assume]] - помоги компилятору сам 😎

Раньше я писал про std::unreachable (он же __builtin_unreachable до C++23) - https://www.tgoop.com/cxx95/58.

Эта штука делает указание компилятору, что в данную ветку исполнения программа никогда не попадет (под личную ответственность программиста), поэтому можно оптимизировать это место.

В C++23 по такому образу стандартизировали похожий функционал: атрибут [[assume(expr)]] (он же __builtin_assume до C++23).

Эта штука делает указание компилятору, что в данной ветке исполнения выражение expr следует считать равным true, и делать разные оптимизации на основе этих данных. Выражение expr вычисляться во время работы программы не будет, это подсказка времени компиляции.

На cppreference (ссылка выше) информации мало, лучше почитать "предложение" о стандартизации: https://wg21.link/p1774r8

Самый простой пример - метод, который делит число на 32:

int div32(int x) {
return x / 32;
}
Казалось бы, очевидная оптимизация - не делить на 32, а сделать битовый сдвиг на 5 битов:
int div32(int x) {
return x >> 5;
}
Но будет неправильно работать на отрицательных числах. Компилятор всегда должен учитывать возможность входа отрицательного числа, из-за этого метод больше по размеру: ссылка на godbolt.

Если программист совершенно точно знает, что все числа будут неотрицательными, то нужно сделать так:
int div32_2(int x) {
[[assume(x >= 0)]]; // или __builtin_assume(x >= 0);
return x / 32;
}
И тогда код оптимизируется: ссылка на godbolt.

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

Некоторые assume можно сделать общими для всего кода (в "предложении" есть пример с умными указателями), но в целом это вещь для узкого круга разработчиков. Есть несколько особенностей этой фичи:

1️⃣ Нужно действительно сильно зависеть от быстродействия программы, например это могут быть реалтаймовые программы. Я однажды кидал видео-выступление Тимура Думлера (автора "предложения") на эту тему - https://www.tgoop.com/cxx95/16.

2️⃣ Нужно понимать, за счет чего срезаются инструкции. Пример программы, которая ограничивает значения массива через std::clamp:
void limiter(float* data, size_t size) {
[[assume(size > 0)]];
[[assume(size % 32 == 0)]];
for (size_t i = 0; i < size; ++i) {
[[assume(std::isfinite(data[i]))]];
data[i] = std::clamp(data[i], -1.0f, 1.0f);
}
}
Предполагая, что размер буфера всегда больше 0 и кратен 32, а флоаты нормализованные, программист ставит assume.
Первый и третий assume не дает делать лишние проверки, а второй assume вероятно как-то связан с кэш-линией процессора.

3️⃣ Нужно постоянно лезть в ассемблер скомпилированной программы и проверять результат - а как иначе? И даже нужно делать юнит-тесты на генерируемый ассемблер (я бы по крайней мере делал). У компиляторов C++ много тестов на получающийся ассемблер, и в отдельных программах с assume они тоже нужны.

4️⃣ Стандарт отмечает, что компиляторы сами вольны оптимизировать код как смогут, никаких требований на них не налагается. Надо проверять, как работает отдельный компилятор и даже отдельная версия, для этого нужны юнит-тесты из 3️⃣ пункта

Можно сделать разные приколы с assume 😁
😱 Фиксируем вариант в switch - ссылка на godbolt.
😱 Решаем простые уравнения с переменной - ссылка на godbolt.

BY C++95


Share with your friend now:
tgoop.com/cxx95/74

View MORE
Open in Telegram


Telegram News

Date: |

5Telegram Channel avatar size/dimensions Image: Telegram. Hui said the time period and nature of some offences “overlapped” and thus their prison terms could be served concurrently. The judge ordered Ng to be jailed for a total of six years and six months. Select “New Channel” More>>
from us


Telegram C++95
FROM American