tgoop.com/thisnotes/198
Last Update:
#cpp
RVO/NRVO 1/2.
Давно хотел рассказать про всякие оптимизации, которые делаются в плюсах. Тут будет пост про RVO/NRVO, а позже про что-нибудь ещё.
Может в начале будут какие-то неявные допущения или грубые формулировки, но к концу поста постараюсь донести все детали, чтобы картина сложилась.
Для начала про терминологию в стандарте. Формально есть общий термин copy elision, который в целом о том, чтобы не делать лишних копий объекта и создавать его сразу там, где нужно. Например ниже примеры copy elision:T t = T(); // not T() + operator= but only T();
T t = T(T(T(T(T(T(T(1))))))); // not T(1) + many T(const T&) + operator= but only T(1)
Вот что говорит стандарт ([class.copy.elision]):When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects.
Т.е. компилятор в праве применять эту оптимизацию в обход copy-/move
-кторов, даже если в них или деструкторе есть какие-то сайдэффекты.
Так же тут есть 4 пункта. В первом говорится, что тип инициализирующего выражения и тип переменной с итоговым результатом должны совпадать. Это главное условие для RVO/NRVO.
RVO/NRVO это частные случаи copy elision, которые происходят в результате возврата чего-либо из функции. Далее мы будем пользовать примерно таким классом, чтобы понять, что происходит с объектами:#define TELL(str) { std::cout << str << std::endl; }
struct S {
S() TELL("def")
S(const S&) TELL("cp")
S(S&&) TELL("mv")
};
Так же есть такой термин как temporary materialization – принцип, согласно которому prvalue-выражение не создаётся физически в памяти до тех пор, пока оно не будет присвоено в не prvalue-объект.
Return Value Optimization по сути и есть пример temporary materialization. Посмотрим на базовый пример (godbolt):S f() { return S(); }
S s = f();
Тут у нас prvalue-выражение S()
, благодаря чему мы можем не создавать переменную в момент его появления в return
и присвоить создать лишь в s
. И из всех интересных нам конструкторов будет вызван лишь конструктор по умолчанию. cv-квалификаторы в сигнатуре функции в данном случае нам ничего не портят (gb).
А если мы можем (не)явно сконструировать возвращаемый тип из выражения в return
? Добавим в S
такой конструктор:S(std::string&&) TELL("str&&")
и собственно пример (gb):S f() { return std::string(10, '1'); }
S s = f();
Всё хорошо, даже не смотря на то, что тип в return
и в сигнатуре не совпадают. По факту компилятор представляет выражение в return
как S(std::string(10, ‘1’))
, что тоже является prvalue. Даже в таком случае всё в порядке (gb): S f() {
std::string s(10, '1');
return s;
}
Всё корректно. Т.к. компилятор справляется тут даже сделать неявный move переменной s
. Причём это всё ещё RVO.
Стоит понимать, что с C++17 RVO — гарантированная штука и даже не оптимизация.
Давайте поймём, как оно работает. У компилятора есть адрес места в памяти, где должен оказаться результат выполнения функции (называется return slot). Когда выполняются условия для RVO, компилятор может сразу создать объект из вашего return
в нужном месте в памяти. Т.е. это скорее про свойства инициализации объектов.
Named Return Value Optimization уже оптимизация (причём не гарантированная, тут компиляторы делают кто что может). Базовый пример (gb):S f() { S s; return s; }
Или пример посложнее, где в S
появился int x
(gb): S f() { S s; s.x = 1; return s; }
void gg(S& s) { s.x = 1; }
S g() {
S s; gg(s); return s;
}
Компилятор всё ещё может создавать локальную переменную s
в функциях сразу в return slot
итогового объекта.
Для NRVO правда volatile
для локальной переменной всё портит (gb).
Почему важно знать об этих штуках? Потому что NRVO можно сломать (gb): S f() {
S s;
return std::move(s); // return S&&
}
Теперь компилятора обязан создать объект и мувнуть его, потому что это то, о чём вы его попросили явно. Так же можно думать, что у выражения в return
и в сигнатуре теперь разные типы (S&&
и S
).
BY this->notes.
Share with your friend now:
tgoop.com/thisnotes/198