8 / 8 / 0
Регистрация: 17.04.2010
Сообщений: 112
|
|||||||||||
1 | |||||||||||
Разница между префиксной и постфиксной формой записи счетчика цикла26.04.2010, 17:56. Показов 30774. Ответов 57
Метки нет (Все метки)
Здравствуйте!
Когда оформлял циклы всегда использовал такую запись:
В последнее время стал встречать другую запись:
Когда какой более предпочтителен?
0
|
26.04.2010, 17:56 | |
Ответы с готовыми решениями:
57
Перегрузка постфиксной и префиксной операции инкремента Перегрузка постфиксной и префиксной формы оператора ++ Отличие постфиксной и префиксной формы декрементирования при работе с массивами Разница между вариантами цикла |
Вездепух
11694 / 6373 / 1723
Регистрация: 18.10.2014
Сообщений: 16,066
|
|
25.02.2016, 04:32 | 41 |
Речь в данном случае идет не о какой-то абстрактной "оптимизации неиспользуемых значений", а о вполне конкретной ситуации: устранении выполнения копирования старого значения объекта внутри нетривиальной реализации постфиксного оператора '++' в ситуации, когда это старое значение не используется снаружи, т.е. в вызове этого оператора.
Оптимизации такого рода весьма и весьма нетривиальны, в том смысле, что для не-inline вызова функций идеального способа выполнения такой оптимизации просто не существует: надо либо делать ветвление внутри реализации оператора, либо генерировать несколько версий оператора. Все варианты обладают своими достоинствами и недостатками.
0
|
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
|
||||||
25.02.2016, 05:03 | 42 | |||||
почти верно. есть нюанс.
компиляторы уже давно научились стандартным RVO/NRVO оптимизациям. и в ситуации, когда они фиксят, что снаружи значение никому не нужно, они запросто могут выпилить конструктор RVO/NRVO вообще то стандартны. их обязаны уметь все компиляторы. и они их умеют уже с незапамятных времен. все очень просто: стандарт разрешает компиляторам покласть болт на любые возможные эффекты в конструкторах копий. а это значит, что компилятору вообще фиолетовы любые побочные эффекты внутри функции. ему достаточно знать её прототип, что бы при необходимости выпилить конструктор копии в каких то опр. случаях. например, когда значение снаружи все равно никому не нужно. я хочу сказать, что совершенно не важно насколько нетривиальна функция. компилятор оптимизирует конструкцию вида:
как и для обычного инта.
0
|
Вездепух
11694 / 6373 / 1723
Регистрация: 18.10.2014
Сообщений: 16,066
|
|
25.02.2016, 05:40 | 43 |
Почти верно, но есть нюанс.
Термины RVO и NRVO в своем каноническом значении относятся именно и только к ситуациям, когда возвращаемое значение именно нужно вызывающему коду. RVO и NRVO описывают стуации, когда внутреннему коду функции разрешается исключать формирование промежуточных локальные объектов (именованных или не именованных), и взамен формировать результат напрямую в объекте-приемнике, предоставленном вызывающим кодом. Ситуации, когда результат вызова функции вообще никому не нужен, непосредственно к RVO и NRVO не относятся. Но если вам нравится включать в RVO и NRVO и такие ситуации - пожалуйста, включайте, никто вам не запретит. Термины RVO и NRVO, как никак, [полу-]неформальны. Тем не менее это ничего не меняет. Реализация оптимизаций для случая игнорируемого значения все равно требует, как я сказал выше, либо run-time branching, либо генерации нескольких версий оператора, либо еще чего-то в этом роде. Ни один из этих вариантов не является "бесплатным". Замечательно Правильнее будет сказать: разрешены стандартом. Но это никоим образом ничего не меняет. К чему это здесь? Нет, конечно. Оптимизация - то всегда вопрос качества реализации (quality-of-implementation). Компилятор вообще имеет право ничего не уметь оптимизировать, а просто тупо транслировать код в строгом соответствии с правилами абстрактной C++-машины. RVO существует "с незапамятных времен". NRVO же существенно более позднее изобретение - его формально не существовало в C++98. Это не верно. Стандарт разрешает компиляторам покласть болт на любые возможные эффекты в конструкторах временных (безымынных) копий. Вы пропустили очень важное слово "временных". На этом принципе было основано copy elision и RVO в С++98. Позже стандарт разрешил "покласть болт" и в ряде частных, четко очерченных случаев для именованных копий, что открыло возможности для NRVO. Но в общем случае ваше утверждение неверно. В общем случае игнорировать конструкторы именованных копий компилятору не разрешается. Опять мимо кассы. Речи идет не о том, что компилятор "может" или "не может", а о том, что его "может" не является бесплатным в сгенерированом коде и выливается либо в run-time branching, либо в code bloat. Это совершено не верно. Оптимизация для типов, чья семантика прекрасно известна на уровне core language, и оптимизация для типов, чья семантика определена на уровне library/user code, отличается принципиально. Т.е. тут вообще даже смешно сравнивать - ничего общего эти оптимизации не имеют и ни о каком "так же просто" речи быть не может. Собственно по этой причине мы и наблюдаем развития языка в сторону NRVO, move semantics и т.п. Эти свойства языка появились именно из-за мешающих оптимизациям принципиальных различий в семантике встроенных и пользовательских типов.
2
|
693 / 303 / 99
Регистрация: 04.07.2014
Сообщений: 846
|
|||||||||||
25.02.2016, 09:08 | 44 | ||||||||||
Рассматривать стоит, например, такой код:
++i для данного итератора делает тоже но быстрее и заменит на него (а если ещё это inline, то оптимизирует ещё лучше). Но если он ничего не знает об итераторе, то выставит то, что уже есть. А теперь посмотрим на стандартный пример реализации инкремента:
{++i;} и {i++;} .
0
|
Вездепух
11694 / 6373 / 1723
Регистрация: 18.10.2014
Сообщений: 16,066
|
||||||
25.02.2016, 09:50 | 45 | |||||
Компилятор так не оптимизирует. Компилятор не имеет права заменить 'i++' на '++i' для невстроенного типа именно потому, что, как вы правильно заметили, компилятор не знает, делают ли эти функции "одно и то же".
Возможности для оптимизации тут кроются в другом. Например, при компиляции вашего постфиксного оператора '++' компилятор может заранее подумать о том, что в некоторых контекстах этот оператор может вызываться с игнорированием результата (вариант A), а в некоторых - с неигнорированием результата (вариант B). По этой причине компилятор может использовать ваше определение постфиксного оператора '++' для того, чтобы втихаря сгенерировать две версии скомпилированного кода: для вариантов A и B отдельно. Внутренние имена для этих двух реализаций будут разные, сгенерированные в соответствии с неким соглашением. Для варианта B оператор будет странслирован честно, как вы написали - с формированием возвращаемого значения. А вот для варианта A будет странслирована "урезанная" версия без возвращаемого значения, которая элементарно соптимизируется до
Вот об этой оптимизации и идет речь. Так, например, работает GCC. Понятно, что за такую оптимизацию приходится платить увеличением размера кода, ибо потенциально каждая функция с не-'void' типом возвращаемого значения может генерироваться в двух вариантах.
0
|
693 / 303 / 99
Регистрация: 04.07.2014
Сообщений: 846
|
|
25.02.2016, 10:12 | 46 |
TheCalligrapher, Это он может сделать, только если видит код, если код скомпилирован в другой объектный файл, то увы, стандартного соглашения об именовании такого урезанного варианта нет.
0
|
Вездепух
11694 / 6373 / 1723
Регистрация: 18.10.2014
Сообщений: 16,066
|
|
25.02.2016, 10:21 | 47 |
Почему это? Нет, сделать это он может всегда и никакого "стандартного соглашения" тут не нужно в принципе. Соглашение тут нужно чисто внутреннее, известное только самому компилятору. Никакой необходимости что-то "стандартизовать" тут нет. Компилятор выбирает это соглашение сам для себя, и никакого кода видеть ему не нужно. Все это прекрасно работает между разными объектными файлами.
0
|
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
|
|||||||||||
25.02.2016, 10:23 | 48 | ||||||||||
не принципиально.
ещё как относится. технология, которая позволяет компилятору удалять код запусков конструкторов. run-time branching - противоречит понятию "оптимизации" по определению понятия "run-time branching". это трудности компилятора, как именно он будет выкручиваться. никаких пенальти в рантайме не будет. на то оно и оптимизация. нет они стандарты. они описаны стандартом. стандарт четко говорит: как , когда, и что должно быть. да, конечно. стандарт - закон для компиляторов. вы решили устроить мне экскурсию в историю? познакомьтесь, ваш "общий случай":
затем допетрит, что obj1 так же образовалось, как не используемое. далее он засунет свои шаловливые ручонки в конструктор obj1. если не дотянеццо - оставит, как есть. если дотянеццо - будет искать сайд эффекты. если их нет - obj1 канет в лету. здесь вы переступили черту здравого смысла. попробуйте искусственно смоделировать: написать код вызова функции. и обозначить два кейса: где возвращаемое значение используется, а значит его нельзя выпиливать. и где не используется, а значит нужно. для наглядности выполните ручную оптимизацию, развернув вашу функцию inline. и вы получите две разные ветки кода. причем в отдельных случаях одну ветку можно реализовать через другую. но это будут две разные нити исполнения. это - единственный способ обеспечить подобную оптимизацию вы можете считать это "платой" за оптимизацию, и полагать это, о боже! "code bloat". только с таким же успехом можно вообще технологию inline так обозвать "code bloat". а здравый смысл подсказывает: inline, манипуляции с абстрактным синтаксическим деревом, все это для того и было создано, что бы избежать пенальти в рантайме, за счет статики. тем, кого парит размер исходного кода и его быстродействие, не пишут всякий бред под капотом единицы трансляции. классическая схема:
move семантика, не более чем вытащенная на юзерский уровень RVO/NRVO
1
|
Вездепух
11694 / 6373 / 1723
Регистрация: 18.10.2014
Сообщений: 16,066
|
|
25.02.2016, 10:38 | 49 |
Это не верно. Если выигрыш от run-time branching превосходит потери на run-time branching - то получается прекрасная оптимизация. Тут все просто.
Нет, ни в коем случае. Это вы, очевидно, с Java перепутали. Стандарт С++ где-то говорит "как, когда, и что должно быть", а где-то не говорит. Стандарт С++ знаменит тем, что содержит огромное количество мест, в которых поведение undefined, unspecified или implementation-defined, т.е. стандартом не оговаривается вообще. А уж об "оптимизациях" даже и говорить смешно - ни о каком "как, когда, и что должно быть" не может быть и речи и ничего подобного в стандарте нет. Кто бы говорил... Тут вообще пошел какой-то поток сознания с приплетением совершенно посторонних вещей, типа inline... Непропарсил. Что это? Этот код - какая-то бессмыслица. К чему это здесь? Ым... Нет, move semantics ничего общего с RVO/NRVO не имеют даже отдаленно. Более того, в контексте возвращаемых значений функции это взаимоисключающие способы оптимизации.
1
|
693 / 303 / 99
Регистрация: 04.07.2014
Сообщений: 846
|
|
25.02.2016, 10:51 | 50 |
Потому что, если метод объявлен не как inline и его реализация хранится в другом файле, то максимум, что может сделать компилятор, так это по всем правилам вызвать конкретную внешнюю функцию, например,
_ZN6NumberppEi .Добавлено через 1 минуту Т.е. выделить место для временного объекта, его получить и разрушить
0
|
Вездепух
11694 / 6373 / 1723
Регистрация: 18.10.2014
Сообщений: 16,066
|
|
25.02.2016, 10:58 | 51 |
Совершенно верно! Поэтому компилятор тихонько сам с собой договаривается, чисто для себя, безо всяких "стандартов", что полноценный метод будет экспортироваться как '_ZN6NumberppEi', а урезанный - как '_ZN6NumberppEikvakva'. И все. Именно так поступает GCC, к примеру.
0
|
693 / 303 / 99
Регистрация: 04.07.2014
Сообщений: 846
|
|||||||||||||||||||||
25.02.2016, 11:35 | 52 | ||||||||||||||||||||
TheCalligrapher, Посмотри на результат, я там ничего не вижу кроме
_ZN6NumberppEi в объектом файле и его вызове в итоговом.Добавлено через 18 минут for.h:
0
|
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
|
|
25.02.2016, 19:07 | 53 |
RVO/NRVO - стандартные оптимизации.
вы никогда не задумывались, почему их называют "стандартными? http://en.cppreference.com/w/c... py_elision священного писания нет под рукой. однако, вы можете сами проверить и убедиться: они там описаны. либо пенальти в рантайме, либо разбухание кода. ну так вот, оптимизация конструкторов устраняет пенальти в рантайме. а что касается разбухания кода - с тем же успехом, можно сказать, что у inline есть своя цена - о боже! inline подстановка приводит к увеличению объемов кода! к тому, что если вы скомпилите это в релизе, оптимизация по скорости, то окажется, что для случаев, когда результат можно отбросить, компилятор оптимизировал простфикс до префикса. определенные в теле класса методы являются inline компилятору доступен контекст. он оптимизирует ненужный временный объект, в результате остается только префикс. то есть, тут даже никакого разбухания кода не происходит. ему ничего не мешает похерить строительство никому не нужного объекта.
0
|
Вездепух
11694 / 6373 / 1723
Регистрация: 18.10.2014
Сообщений: 16,066
|
|
25.02.2016, 20:05 | 54 |
Их называют стандартными потому, что они разрешены стандартом.
О том, что они там описаны вам сообщил я. Причем я также сообщил вас, как они там описаны: именно как оптимизации, т.е. что-то, что компилятор может, но не обязан делать. Поэтому зачем вы нам тут пытаетесь напускать туману глубокомысленными разглагольствованиями о том, что "оптимизации называют стадартными" и что "они там описаны" - мне не понятно. Мы все здесь об это прекрасно знаем. К чему это здесь? Вы нам тут обширно разглагольствовали на тему того, что станадарт обязывает компиляторы делать такие оптимизации и даже говорит где и когда они обязаны это делать. Поэтому не надо нам тут мозги пудрить томными упоминаниями каких-то названий и описаний, а давайте-ка покажите нам, где это конкретно стандарт обязывает компиляторы выполнять оптимизации. Прямые цитаты из стандарта приведите, пжлст. Вопрос, конечно, риторический. Ничего подобного, разумеется, в стандарте нет. На этой замечательной ноте я закрываю это направление обсуждения. Совершенно верно. Именно поэтому inline-подстановка не выполняется безусловно, а основана на решении, принимаем компилятором на основе каких-то внутренних эмпирических/эвристических критериев, в том числе учитывающих вопросы разбухания кода и конфигурационные параметры, заданные пользователем. Компилятор может решить выполнить inline-подстановку, а может решить не выполнить. Другими словами, в общем случае inline-подстановки не делается и рассчитывать на то, что она произойдет - нельзя. То же самое относится и к рассматриваемому вопросу: сколько бы вы ни придумывали оптимизаций, скрывающих разницу в производительности между префиксным и постфиксным инкрементом, помните, что по той или иной причине в общем случае эти оптимизации выполняться не будут. Еще раз: приведенный вам код некорректен и бессмысленен. Поэтому никакого разговора о каких-то оптимизациях в отношении этого кода быть не может. Возьмите в привычку читать свой код внимательнее, перед тем как постить его на всеобщее обозрение, чтобы в будущем избегать подобных конфузов.
0
|
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
|
||||||
25.02.2016, 20:39 | 55 | |||||
нет, их называют стандартными, потому что они стандартизированы.
ваш Кэп. стандарт вообще никому ничего не запрещает. Кэпа можно не благодарить. к тому, что компиляторы обязаны уметь выполнять такие оптимизации. и они это делают. там выше я привел ссылку на референс. и ссылку на параграф. на решении программиста. программист сказал - максимальная скорость, и понеслось... компилятор выполняет просьбу программиста всегда, когда есть такая техническая возможность, и это не противоречит стратегии оптимизации. если выставленна настройка "оптимизировать по скорости", то причиной отказа от инлайна может послужить оптимизация связанная с кэшем и конвейерами процессора, например. это ситуация, когда перфоманс не страдает. и возвращаясь к нашим баранам - по классической схеме постфикс оптимизируется до префикса на ура.
0
|
Модератор
|
|
25.02.2016, 21:23 | 56 |
hoggy, вот тут лежит черновик стандарта C++14 (Document Number: N3797).
1
|
Игогошка!
1801 / 708 / 44
Регистрация: 19.08.2012
Сообщений: 1,367
|
|
25.02.2016, 22:34 | 57 |
hoggy, сможешь продемонстрировать, что, например, постфикс от std::istream_iterator<std::string>действительно оптимизируется всеми распространенными компиляторами?
0
|
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
|
|
25.02.2016, 23:22 | 58 |
0
|
25.02.2016, 23:22 | |
25.02.2016, 23:22 | |
Помогаю со студенческими работами здесь
58
Есть ли разница в оптимизации между определением переменной до цикла Написать программу, преобразующую строку в префиксной форме в строку в постфиксной форме В чем разница между этими двумя способами записи? построить выражение в префиксной записи Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |