Open apolukhin opened 3 years ago
копать во все это?, 40 за Обсудили идею на встрече https://events.yandex.ru/events/cpp-party/15-jun-2018/ . Далее - стенограмма обсуждения:
Ultimate copy elisions aaaand Subobjects copy elision
Не вызывать копи или мув конструктор Ограниченно сильно
В RVO
struct T {
T() noexcept; T(T&&) noexcept; ~T() noexcept;
void do_something() noexcept;
};
...
T d = shrink(update(produce()));
Простыня асма. Фукнции заинлайнены. Объект создается.
Создает новую переменную, это плохо Несколько раз делает.
Вместо копирования Y в X можно переиспользовать Y. Используется copy elision. Код уменьшается. Нет лишних деструкторов и конструкторов. Ослабить требования. Позволить смешивать оптимизации.
Поднять опт. с middle до front? Или copy elision в middle?
Какие проблемы решаются? Код уменьшается, больше оптимизаций. => время компиляции. Производительность выше, меньше бинари. Проще учить плюсам людей, не надо думать о std::move на первых порах.
Продвинутое использование:
return path(__lhs) /= __rhs; // copy elision запрещен, not compile
return pair.second; // вызвать destructor от pair.first и продолжить
raturn get<0>(tuple); // same
auto [a,b] = foo(); return a; // same
return local_aggregate_variable.name // same
Работает часто только для inline Проблемы для не trivially_destructible
return stringstream.str();
return get<int>(variant);
return path.string();
Not only for function returns All together Copy elision разрешен для любого non volatile объекта, распол. на стеке и его полей, если source объект не используется перед конструированием.
How far we should go? Речь о возврате по значению.
Предложение скорее всего ломает пользовательский код, но скорее всего только не переносимый. Если нарушить условия, код нельзя перенести с одного компилятора на другой и даже на другую версию одного компилятора и нельзя переносить между импл. stl. Если ваши объекты посли копи или мув не равны, то вы неправильно пишите код. Алгоритмы не будут работать с такими типами, который нарушают эти условия. Уже сущетсвующие правила copy elision позволяют делать такие вещи. Уже сейчас нужно осторожно использовать pmr. Вывод: пишите regular типы. У вас и так всё сломается, лучше не пишите на С++
Вопрос о странном конструкторе, который передает указатели на несуществующие объекты в свой подклассы. Ответ: в gcc такое поведение - UB. В любом случае так лучше не делать. Денис: мы говорим только про return и rvalue? Ответ: нет
Вопрос: пусть есть несколько локальных объектов, а ты возвр. тупл из этих объектов. Я не буду использовать эти объекты больше. Этот use case сюда входит? Ответ: входит, но это пункт о передачи аргументов в функцию, она сложная Вопрос: если мы не можем делать все честно, можно ли просто сделать конвертацию в x-value? Ответ: Да, по сути можно, нужно подумать еще Вопрос: Что с типами, внутри которых union? Ответ: то же самое. Копирования его не будет Денис: смущает отсутствие адреса. Быстро говорит. Отсутствие адреса смущает Ответ: Надо подумать. Ответ не особо то есть Вопрос: неверное направление. Компиляторы же уже могут это делать? Ответ: нет, не умеют. Вопрос: может нужно дать больше информации компилятору? На уровне стандарта. Разве сейчас уже нельзя выкинуть new и delete? Ответ: Да, сейчас что-то можно, но не так, как хотелось бы. То есть конструкторы создаются, но могут не использоваться. Наша цель - убрать конструкторы. Вопрос: Пусть есть динамик объект. С 1 по 2 пункт не будет нигде гарантии о возвращении под из не под объекта. Ответ: для таких объектов нельзя такого делать. Json аллоцирует память, работать не будет Вопрос: а для таких json, которые на стеке и все такое? Ответ: Не работает в случаях, если конструкторы меняют стейт программы. Вопрос: про асм. Представляю немного об этом. Как объекты на стеке выделяются. Для функции мы сдвигаем стек поинтер. Вы сделали большой объект. Нам потом нужно убрать большой кусок, но оставить кусок для подобъекта. Ответ: работает только для за-inline-неных. компилятор может это разрулить Вопрос: Но ведь нарушается порядок обращения к полям Ответ: не особо понятно, о чем вы говорите. Если функция не inline, то говорить не о чем. Передвигать поля не нужно.
Ответ: elision - это убирание мув и копи конструктра. Вопрос: не получится ли так, что при попытке заинлайнить деструктор, сген. компилем, что эффект уменьшения кода будет компенс. необходимостью многое удалять Ответ: все зависит от разработчиков компилятора. Все зависит от них. Может только с О3, а может и с О0 Денис: что такое dd Ответ: знать, что написано = default. Или что его нет вообще. Денис: я отнасл. от вектора. Ответ: хз
Не только для ретурнов: Вопрос: пусть где-нибудь исключение и раскрутка стека. Как будет соглас. все удаления. Ответ: таблицы свертки будут меньше, за счет того, что будет меньше кода. А дальше все компилятор думает. Вопрос: часто говорите о компилятора Ответ: ну да, им лучше знать Вопрос: сейчас take by copy берет значение по копии. То, что вы обозначили на первых строчках решается с использованием universal reference. То есть мы научилсь договаривать. Ответ: ну да, можно сделать мув. Но если работаем со старым кодом, ничего не получится улучшить. Если работаем с кодом, где пользователь использовал мув, то компилятор и так соптимизирует. Вопрос: может поговорим о том, что если переменная используется последний раз, то пусть и так будет std::move. Ответ: а если это цикл? и тд Вопрос: Я этого так ждал! Ответ: ура... Денис: У двух копий должны быть разные адреса. Или все ломается. Ответ: Сейчас работает в rvo так работает Денис: если раньше взяли адрес от переменной, которую потом хотим заслать Ответ: сложно, нужно следить за временем жизни. Эт рили сложно. Если адресная арифметика, компилятор может не делать ничего. Если ему сложно. Денис: Оч мало случаев, когда адреса можно сравнивать Ответ: ну да Вопрос: Не понятно время жизни объекта, который внутрь инлайнится. Допустим T - это гард. Его деструктор в каком блоке вызывается? В функции или в блоке, из которого вызвана? Ответ: функция заинлайнится, поэтому будет общий скоуп. Опять же, обязана заинлайниться. Вопрос: как меняется время жизни объекта? в 3 пункте. Про умные указатели говорю. Там всякие каунтеры. Ответ: надо как-то жестче специфицировать, как деструкторы должны пересекаться. Иначе в этом случае временный объект живет до конца выражения. Может нельзя деструкторы менять местами. Тут должен любой деструктор срабатывать. Вопрос: про смарт птр. Я понимаю, что все работает со стандартными. А если не стандартные. Какая-то жесть. Ответ: если у тебя что-то странное с внешними эффектами, не делай так. Покажи этот код ПОКАЖИ ЕГО НАМ Вопрос: Я придумал юз кейс, когда код может сломаться, который раньше работал. Если тип Т - это некий таймер, который при констр. взводиться и в дестр. записывает в лог. То в первой строчке вызовится не там и 2 раза. Ответ: обычно таймеры не копируются. Вопрос: но иногда копируются Ответ: покажите нам этот код, пришлите нам, мы на него посмотрим. Вопрос: может просто добавить аттрибут Ответ: есть такая штука volatile, то для нее ничего не сработает Вопрос: протухший код можно детектить? Ответ: можно, будет млн сообщений и придется кучу кода менять. Вообще уже в стандарте кучу кода придется менять, делать trivially_destructible и тд. Вопрос: думал, как сломать. Договорились, что сайд эффекты - это плохо. Допустим я считаю в компайлтайме. Так уже можно copy elision? Ответ: да, там уже это можно Вопрос: не может ли за из-за copy elision в ран и компайл тайме отличаться поведение Ответ: это возможно уже сейчас
Голосование:
15 почти за, 5 нейтрально 0 против, 0 строго против , 1 пункт: => копай дальше
17 за, 27 почти за 3 нейтрально, 0 против , 2 пункт: 0 строго против
нужно ли копать в него?, 15 за нет смысла голосовать
15 почти за, 6 нейтрально 3 против, 2 строго против , 4 пункт(посылать в функции аргументы): Человек: против, потому что нарушает дух языка. С выиграл, потому что язык, который работал везде и позволял запихать биты туда, куда надо. Выше предложение это нарушает. Духа нет. Бездушно. Ответ: Свойство это уже сейчас есть. С++ такой. Антон: Не согласен, так как все оптимизации, невидимые пользователю, разрешены.
Человек: я против, потому что не понимаю, как это можно сделать
26 за, 24 почти за 7 нейтрально, 4 против yndx-antoshkka, 25 июня 2018, 12:38 0 строго против
Mikhail Shostak, 27 июня 2018, 0:22 Обновлено 25 июня 2018, 12:41
Тут попался код из std::thread:
std::invoke(decay_copy(std::forward<Function>(f)), decay_copy(std::forward<Args>(args))...);
where decay_copy is defined as
template <class T>
std::decay_t<T> decay_copy(T&& v) { return std::forward<T>(v); }
https://en.cppreference.com/w/cpp/thread/thread/thread
Я так понимаю с этим предложением подобный код может поломаться и перестать копировать. Но, видимо, предложенный copy elision в случае с потоками не будет срабатывать даже если там всё заинлайнится т.к. decay_copy будет вызывается на стеке нового потока и f и args уже не будут являться "локальными" переменными. Возможно где-то еще есть подобное копирование, но где такое может понадобиться с ходу придумать не получилось.
PoC здесь https://github.com/rusyaev-roman/llvm-project/tree/ultimate_copy_elision_v4.
Что осталось:
Сейчас есть несколько известных ошибок, которые удалось локазиловать. Исправление нужно сделать аккуратным, т.к исправление в лоб может повлиять на время компиляции.
Будет свободный промежуток с 19.04.21 по 25.04.21, в который постраюсь завершить работу.
Еще нужно бы открыть issue по обсуждению пропозала на добавление атрибута nocapture (который обсуждали здесь). Открою, когда выложу патч по UCE на фабрикатор
Перенос предложения: голоса +35, -0 Автор идеи: yndx-antoshkka
Правила copy elision в секции [class.copy.elision] отлично работают!
Однако они подразумевают, что функция в исходном коде будет являться функцией и в бинарнике. Что не верно, для оптимизирующих компиляторов, умеющих встраивать тела функций и делать link-time оптимизации.
Для таких компиляторов в ассемблерном коде программы зачастую можно встретить цепочки копирования+удаления объекта. Другими словами можно увидеть нечто подобное после встраивания функций:
Подобные цепочки всегда генерируются при:
В https://apolukhin.github.io/papers/ultimate_copy_elision.html предлагаю убрать существующие ограничения на copy elision и всегда позволять его делать если: копируемый локальный не volatile объект или его не volatile субобъект не используются после копирования.
Такая формулировка не влияет на совместный scope объектов: Если код после inline выглядел как
То с убранными ограничениями для copy elision код превратится в
Подобная оптимизация может положительно сказаться на времени компиляции и на время link-time оптимизации: в данный момент компиляторы пытаются встраивать вызов copy конструктора и деструктора и оптимизировать всё их содержимое в контексте текущей функции. Это ведёт к тому, что оптимизатор вынужден оптимизировать несколько мегабайт низкоуровнего кода, вместо того, чтобы просто выкинуть эти два вызова функций и работать с намного меньшими объёмами низкоуровневых команд.