Какую оптимизацию предлагает const в C/C++?
Я знаю, что, когда это возможно, вы должны использовать ключевое слово const при передаче параметров по ссылке или по указателю для удобства чтения. Есть ли какие-либо оптимизации, которые может выполнить компилятор, если я укажу, что аргумент постоянный?
Может быть несколько случаев:
Параметры функции:
Постоянная ссылка:
void foo(const SomeClass& obj)
Постоянный объект SomeClass:
void foo(const SomeClass* pObj)
И постоянный указатель на SomeClass:
void foo(SomeClass* const pObj)
Объявления переменных:
const int i = 1234
Объявление функций:
const char* foo()
Какую оптимизацию компилятора предлагает каждый (если есть)?
Ответы
Ответ 1
[Читатели учтут, что большая часть этой публикации снята из статьи Херба Саттера - http://www.gotw.ca/gotw/081.htm - без атрибуции OP.]
CASE_1: -
Когда вы объявляете const в своей программе,
int const x = 2;
Компилятор может оптимизировать этот const, не обеспечивая хранение этой переменной, а добавляя ее в таблицу символов. Таким образом, последующее чтение просто нуждается в косвенности в таблице символов, а не в инструкциях для извлечения значения из памяти.
ПРИМЕЧАНИЕ. - Если вы делаете что-то вроде: -
const int x = 1;
const int* y = &x;
Затем это заставит компилятор выделить пространство для 'x'
. Таким образом, эта степень оптимизации для этого случая невозможна.
В терминах параметров функции const
означает, что параметр не изменяется в функции. Насколько я знаю, нет существенного прироста производительности для использования const
, а это средство для обеспечения правильности.
CASE_2: -
"Объявляет ли параметр и/или возвращаемое значение const, чтобы компилятор мог создать более оптимальный код?"
const Y& f( const X& x )
{
// ... do something with x and find a Y object ...
return someY;
}
Ques = > Что может сделать компилятор лучше?
= > Не удалось ли копировать параметр или возвращаемое значение?
Нет, поскольку аргумент уже передан по ссылке.
= > Может ли он поместить копию x или someY в постоянную память?
Нет, поскольку и х, и некоторые из них живут вне его сферы действия и поступают из и/или передаются во внешний мир. Даже если какой-либо Я динамически выделяется "на лету" внутри самого f(), он и его собственность передаются вызывающему.
Ques = > Как насчет возможных оптимизаций кода, который появляется внутри тела f()? Из-за const, может ли компилятор каким-то образом улучшить код, который он генерирует для тела f()?
Даже когда вы вызываете функцию-член const, компилятор не может предположить, что биты объекта x или объекта someY не будут изменены. Кроме того, есть дополнительные проблемы (если компилятор не выполняет глобальную оптимизацию): компилятор также может не знать наверняка, что ни один другой код не может иметь неконстантную ссылку, которая псевдонизирует тот же объект, что и x и/или someY, и есть ли такой неконстантные ссылки на один и тот же объект могут использоваться случайно во время выполнения f(); и компилятор может даже не знать, действительно ли реальные объекты, к которым x и некоторые Y являются просто ссылками, фактически были объявлены как const.
CASE_3: -
void f( const Z z )
{
// ...
}
Ques = > Будет ли в этом оптимизация?
Да, потому что компилятор знает, что z действительно является объектом const, он может выполнять некоторые полезные оптимизации даже без глобального анализа. Например, если тело f() содержит вызов типа g (& z), компилятор может быть уверен, что не изменяемые части z не изменяются во время вызова g()
Ответ 2
Прежде чем дать какой-либо ответ, я хочу подчеркнуть, что причина использования или не использования const
действительно должна быть для правильности программы и для большей ясности для других разработчиков, а не для оптимизации компилятора; то есть создание параметра const
документов, что метод не будет изменять этот параметр, и делает функцию-член const
документом, что этот член не будет изменять объект, членом которого он является (по крайней мере, не так, логически изменяет вывод из любой другой функции-члена-константы). Например, это позволяет разработчикам избежать ненужных копий объектов (потому что им не нужно беспокоиться о том, что оригинал будет уничтожен или изменен) или во избежание ненужной синхронизации потоков (например, зная, что все потоки просто читают и делают не мутировать объект, о котором идет речь).
Что касается оптимизаций, которые компилятор мог бы сделать, по крайней мере теоретически, хотя и в режиме оптимизации, что позволяет ему выполнять определенные нестандартные предположения, которые могут нарушить стандартный код на С++, рассмотрим:
for (int i = 0; i < obj.length(); ++i) {
f(obj);
}
Предположим, что функция length
отмечена как const
, но на самом деле является дорогостоящей операцией (предположим, что она фактически работает в O (n) раз, а не O (1) раз). Если функция f
принимает свой параметр с помощью ссылки const
, тогда компилятор может потенциально оптимизировать этот цикл для:
int cached_length = obj.length();
for (int i = 0; i < cached_length; ++i) {
f(obj);
}
... поскольку тот факт, что функция f
не изменяет параметр, гарантирует, что функция length
должна возвращать одинаковые значения каждый раз, когда объект не изменился. Однако, если объявлено, что f
принимает параметр с помощью изменяемой ссылки, тогда length
нужно будет пересчитать на каждой итерации цикла, так как f
мог бы изменить объект таким образом, чтобы произвести изменение в значение.
Как указано в комментариях, это предполагает ряд дополнительных предостережений и будет возможно только при вызове компилятора в нестандартном режиме, что позволяет ему делать дополнительные предположения (например, методы const
строго функция их входов и оптимизации можно предположить, что код никогда не будут использовать const_cast
преобразовать константный ссылочный параметр на изменяемую ссылку).
Ответ 3
Параметры функции:
const
не имеет значения для ссылочной памяти. Это как привязка руки за оптимизатором.
Предположим, вы вызываете другую функцию (например, void bar()
) в foo
, которая не имеет видимого определения. Оптимизатор будет иметь ограничение, поскольку он не знает, изменил ли параметр bar
параметр функции, переданный в foo
(например, через доступ к глобальной памяти). Потенциал для изменения внешней памяти и псевдонимов создает значительные ограничения для оптимизаторов в этой области.
Хотя вы не спрашивали, значения const
для параметров функции позволяют оптимизировать, поскольку оптимизатору гарантирован объект const
. Конечно, стоимость копирования этого параметра может быть намного выше, чем преимущества оптимизатора.
Смотрите: http://www.gotw.ca/gotw/081.htm
Переменные объявления: const int i = 1234
Это зависит от того, где он объявлен, когда он создан, и типа. Эта категория в значительной степени существует там, где существуют оптимизации const
. undefined изменять объект const
или известную константу, поэтому компилятору разрешено выполнять некоторые оптимизации; предполагается, что вы не вызываете поведение undefined и получаете некоторые гарантии.
const int A(10);
foo(A);
// compiler can assume A not been modified by foo
Очевидно, что оптимизатор также может идентифицировать переменные, которые не меняются:
for (int i(0), n(10); i < n; ++i) { // << n is not const
std::cout << i << ' ';
}
Объявление функций: const char* foo()
Не важно. Зарезервированная память может быть изменена извне. Если ссылочная переменная, возвращаемая foo
, видна, оптимизатор может сделать оптимизацию, но это не имеет ничего общего с наличием/отсутствием const
в возвращаемом типе функции.
Опять же, значение или объект const
отличается:
extern const char foo[];
Ответ 4
SomeClass* const pObj
создает постоянный объект типа указателя. Существует не безопасный метод изменения такого объекта, поэтому компилятор может, например, кэшировать его в регистр с чтением только одного считывания, даже если его адрес занят.
Другие не включают в себя какие-либо оптимизации, хотя спецификатор const
в этом типе будет влиять на разрешение перегрузки и, возможно, приведет к выбору различных и более быстрых функций.
Ответ 5
Точные эффекты const различаются для каждого контекста, в котором он используется. Если const используется при объявлении переменной, она физически const и сильно находится в постоянной памяти.
const int x = 123;
Пытаться отбросить консистенцию будет undefined behavour:
Даже несмотря на то, что const_cast может удалить константу или волатильность от любого указателя или ссылки, используя результирующий указатель или ссылку на запись в объект, который был объявлен const или для доступа к объявленному пользователем volatile вызывает поведение undefined. cppreference/const_cast
Итак, в этом случае компилятор может предположить, что значение x
всегда 123
. Это открывает некоторый потенциал оптимизации (распространение констант)
Для функций это другое дело. Пусть:
void doFancyStuff(const MyObject& o);
наша функция doFancyStuff
может выполнять любую из следующих операций с помощью o
.
- не изменять объект.
- отбросить константу, а затем изменить объект
- измените
mutable
элемент данных MyObject
Обратите внимание, что если вы вызываете нашу функцию с экземпляром MyObject, который был объявлен как const, вы будете вызывать поведение undefined С# 2.
Вопрос с гуру: будут ли следующие вызовы undefined?
const int x = 1;
auto lam = [x]() mutable {const_cast<int&>(x) = 2;};
lam();