Мотивация оптимизации пересекающихся областей памяти
В предыдущей статье было рассказано о правиле
Давайте «поиграем в компилятор» и попробуем понять логику оптимизации этой функции:
Итак, можем ли мы оптимизировать данный код?
С одной стороны, мы можем предположить, что раз мы задали
Давайте попробует измерить с помощью игрушечного бенчмарка, будет ли это как-то влиять? Опустим момент, почему я его запустил с опцией
С другой стороны, если
Вышеописанная ситуация может показаться неоднозначной. В большинстве случаев, скорее всего, она будет легальна, но для каких-то совершенно недопустима. В случае, если бы вы разрабатывали свою программу, то вы бы могли решить этот вопрос просто посмотрев, где и как используется такая функция.
Но вообще говоря, это касается не только вырванных из контекста функций с их аргументами, а любых указателей и ссылок на пересекающуюся память. Можно ли подставлять значения в последующих выражениях без перечитывания памяти?
Для вашего компилятора это одновременно и возможность офигенно ускорить исполнение программ, и головная боль: как понять, происходит наложение памяти (aliasing) и оптимизироваться нельзя?
Попробуем рассуждать дальше. Условно, этапе компиляции можно попробовать отследить какие адреса должны быть в указателях. Но вопрос особенно остро встаёт, когда мы компилируем какую-то функцию для динамической библиотеки. По сути, перед нами только сама функция и всё! Контекста вызова этой функции нет. У компилятора не так много информации, которую он может использовать, поэтому в качестве критерия выступает тип ссылки/указателя. Вероятно, что представление пересекающейся памяти совершенно разными типами - это все таки очень редкий случай в рамках одного приложения.
Комитет стандартизации C/С++ предпочел регламентировать правила, по которым можно не ограничивать программистов и предоставить лучшую производительность. Компромиссный вариант.
Компилятор не может применить оптимизацию, если соблюдается правило strict aliasing. Во всех остальных случаях компилятор по умолчанию считает любые указатели/ссылки непересекающимися областями памяти и будет их оптимизировать, если не удается явно детектировать его нарушение... Это можно сделать, преимущественно в рамках тела одной функции, когда есть полный контекст взаимодействия с указателем. В этом случае будут сгенерированы инструкции, аналогичные корректному коду: живой пример. Вероятно, именно поэтому с такой проблемой разработчики сталкиваются реже, чем могли бы.
Но вот можно ли рассчитывать на то, что в другой версии компилятора это будет работать точно так же? Раз не стандартизировано и это UB, значит нет. В общем случае, такой код становится непереносимым.
#compiler
В предыдущей статье было рассказано о правиле
strict aliasing
. Предлагаю сегодня немного порассуждать, почему оптимизация имеет место быть?Давайте «поиграем в компилятор» и попробуем понять логику оптимизации этой функции:
auto set_default(int &ival, float &dval)
{
ival = 0;
dval = 2.0;
return std::pair(ival, dval);
}
Итак, можем ли мы оптимизировать данный код?
С одной стороны, мы можем предположить, что раз мы задали
ival
и dval
конкретные константы и больше никаких операций с этими ссылками не делали, то мы можем заранее вычислить объект std::pair(ival, dval)
вот так:auto set_default(int &ival, float &dval)
{
ival = 0;
dval = 2.0;
// Вычислим в compile time
constexpr auto result = std::pair(0, 2.0);
return result;
}
Давайте попробует измерить с помощью игрушечного бенчмарка, будет ли это как-то влиять? Опустим момент, почему я его запустил с опцией
-O0
, но мы видим прирост на ~30%. Получается, что наша ручная оптимизация имеет значение. И это лишь одна оптимизация, которая может комбинироваться с другими.С другой стороны, если
ival
и dval
ссылаются на пересекающиеся области памяти, то мы явно понимаем, что наша функция должна возвращать совершенно другие значения. Вот вам живой пример для наглядности.Вышеописанная ситуация может показаться неоднозначной. В большинстве случаев, скорее всего, она будет легальна, но для каких-то совершенно недопустима. В случае, если бы вы разрабатывали свою программу, то вы бы могли решить этот вопрос просто посмотрев, где и как используется такая функция.
Но вообще говоря, это касается не только вырванных из контекста функций с их аргументами, а любых указателей и ссылок на пересекающуюся память. Можно ли подставлять значения в последующих выражениях без перечитывания памяти?
Для вашего компилятора это одновременно и возможность офигенно ускорить исполнение программ, и головная боль: как понять, происходит наложение памяти (aliasing) и оптимизироваться нельзя?
Попробуем рассуждать дальше. Условно, этапе компиляции можно попробовать отследить какие адреса должны быть в указателях. Но вопрос особенно остро встаёт, когда мы компилируем какую-то функцию для динамической библиотеки. По сути, перед нами только сама функция и всё! Контекста вызова этой функции нет. У компилятора не так много информации, которую он может использовать, поэтому в качестве критерия выступает тип ссылки/указателя. Вероятно, что представление пересекающейся памяти совершенно разными типами - это все таки очень редкий случай в рамках одного приложения.
Комитет стандартизации C/С++ предпочел регламентировать правила, по которым можно не ограничивать программистов и предоставить лучшую производительность. Компромиссный вариант.
Компилятор не может применить оптимизацию, если соблюдается правило strict aliasing. Во всех остальных случаях компилятор по умолчанию считает любые указатели/ссылки непересекающимися областями памяти и будет их оптимизировать, если не удается явно детектировать его нарушение... Это можно сделать, преимущественно в рамках тела одной функции, когда есть полный контекст взаимодействия с указателем. В этом случае будут сгенерированы инструкции, аналогичные корректному коду: живой пример. Вероятно, именно поэтому с такой проблемой разработчики сталкиваются реже, чем могли бы.
Но вот можно ли рассчитывать на то, что в другой версии компилятора это будет работать точно так же? Раз не стандартизировано и это UB, значит нет. В общем случае, такой код становится непереносимым.
#compiler