Как не указывать точный порядок оценки аргумента функции помогает компилятору C & C++ генерировать оптимизированный код?

#include <iostream>
int foo() {
    std::cout<<"foo() is called\n";
    return 9;
}
int bar() {
    std::cout<<"bar() is called\n";
    return 18;
}
int main() {
    std::cout<<foo()<<' '<<bar()<<' '<<'\n';
}
// Above program's behaviour is unspecified
// clang++ evaluates function arguments from left to right: http://melpon.org/wandbox/permlink/STnvMm1YVrrSRSsB
// g++ & MSVC++ evaluates function arguments from right to left
// so either foo() or bar() can be called first depending upon compiler.

Вывод вышеуказанной программы зависит от компилятора. Порядок, в котором оцениваются аргументы функции, не указан. Причина, по которой я читал об этом, заключается в том, что это может привести к высокооптимизированному коду. Как не указание точного порядка вычисления аргумента функции помогает компилятору генерировать оптимизированный код?

Насколько мне известно, порядок оценки строго указан в таких языках, как Java, C#, D и т. д.


person Destructor    schedule 04.05.2016    source источник
comment
На самом деле указано, что каждый оп-вызов — это отдельный (сцепленный) вызов. Они будут оцениваться в порядке, определяемом приоритетом оператора.   -  person StoryTeller - Unslander Monica    schedule 04.05.2016
comment
@StoryTeller: Вы ошибаетесь.   -  person Lightness Races in Orbit    schedule 04.05.2016
comment
@StoryTeller: Это верно для порядка вызова функций. Но это неверно в отношении порядка, в котором оцениваются параметры. bar() и foo() могут быть оценены до того, как будут сделаны какие-либо вызовы. Единственное требование состоит в том, чтобы они оценивались до точки следования, где они необходимы.   -  person Martin York    schedule 04.05.2016
comment
@LightnessRacesinOrbit, каждый вызов оператора требует, чтобы его параметры оценивались перед вызовом, поскольку вызов является вложенным, здесь не так уж много свободы.   -  person StoryTeller - Unslander Monica    schedule 04.05.2016
comment
@StoryTeller: параметры могут быть оценены до того, как будет сделан какой-либо вызов, потому что все это одно выражение. Точка следования начинается в начале выражения и заканчивается точкой с запятой. Параметры упорядочиваются перед вызовом, но не требуется, чтобы они упорядочивались непосредственно перед вызовом.   -  person Martin York    schedule 04.05.2016
comment
@LokiAstari, каждый вызов оператора возвращает параметр для следующего. Единственная свобода — это вызовы foo() и bar().   -  person StoryTeller - Unslander Monica    schedule 04.05.2016
comment
@StoryTeller: все операнды могут быть оценены (в любом порядке) перед любым вызовом.   -  person Lightness Races in Orbit    schedule 04.05.2016
comment
@StoryTeller: каждый вызов оператора возвращает параметр для следующего Левый операнд, да.... не правый, который в каждом случае нас интересует.   -  person Lightness Races in Orbit    schedule 04.05.2016
comment
@StoryTeller: Вот о чем вопрос. foo() и bar могут располагаться перед всеми вызовами. Или непосредственно перед их использованием после выполнения других вызовов. Они могут располагаться в любом порядке по отношению друг к другу.   -  person Martin York    schedule 04.05.2016
comment
@LightnessRacesinOrbit: Что не так с моим вопросом?   -  person Destructor    schedule 04.05.2016
comment
Короче говоря. Если порядок оценки имеет значение, сохраните его, предварительно оценив все параметры и кэшируя результаты.   -  person StoryTeller - Unslander Monica    schedule 04.05.2016
comment
@Destructor, по крайней мере, для меня вопрос кажется неоднозначным, возможно, из-за языкового барьера. Вы хотели спросить: как неопределенный порядок оценки аргументов позволяет компилятору оптимизировать код или как не указывать точный порядок оценки, чтобы помочь компилятору генерировать оптимизированный код? Если первое, я бы порекомендовал также спросить Разрешает ли оставлять порядок оценки аргументов неопределенным, компилятор может оптимизировать код, что я не думаю, что можно предположить. Кроме того, если вы ссылаетесь на что-то, что вы прочитали, пожалуйста, также укажите, где вы это прочитали.   -  person eerorika    schedule 04.05.2016
comment
@Destructor: Ничего. Я думаю, что это отличный вопрос.   -  person Lightness Races in Orbit    schedule 04.05.2016
comment
@LightnessRacesinOrbit: спасибо за высокую оценку моего вопроса!!!   -  person Destructor    schedule 04.05.2016
comment
Есть причина, по которой C оценивал аргументы и помещал их в стек в порядке справа налево. Причина была не в оптимизации. Это было сделано для того, чтобы вы могли иметь вариативные функции, такие как printf. Крайние левые аргументы будут помещены в стек последними и, таким образом, будут иметь известное смещение относительно указателя стека. (Это также требовало, чтобы вызывающий, а не вызываемый объект корректировал стек по возвращении, потому что только вызывающий объект знает, сколько было отправлено.)   -  person Mike Dunlavey    schedule 04.05.2016
comment
@Destructor: пожалуйста. Спасибо, что написали это!   -  person Lightness Races in Orbit    schedule 04.05.2016


Ответы (4)


Я думаю, что вся предпосылка вопроса неверна:

Как не указывать точный порядок оценки аргумента функции помогает компилятору C & C++ генерировать оптимизированный код?

Речь идет не об оптимизации кода (хотя это и допускается). Речь идет о том, чтобы не наказывать компиляторы, потому что базовое оборудование имеет определенные ограничения ABI.

Некоторые системы зависят от параметров, помещаемых в стек в обратном порядке, в то время как другие зависят от прямого порядка. C++ работает на всех типах систем со всеми типами ограничений. Если вы применяете порядок на уровне языка, вам потребуется, чтобы некоторые системы заплатили штраф за соблюдение этого порядка.

Первое правило C++: «Если вы не используете его, вам не нужно платить за это». Таким образом, выполнение приказа было бы нарушением основной директивы C++.

person Martin York    schedule 04.05.2016
comment
+5 за напоминание о философии C++: Если вы его не используете, вам не придется за него платить - person Destructor; 04.05.2016
comment
Если бы у вас был принудительный порядок оценки, точки последовательности заставили бы вас сделать этот вызов дважды. Не совсем. Как-будто все еще применимо. - person T.C.; 04.05.2016
comment
Что вы имеете в виду под наказанием? На самом деле речь идет о предоставлении дополнительных возможностей для оптимизации кода. Кроме того, какие ограничения ABI есть у оборудования? ОС и компилятор определяют ABI. Кроме того, вы неправильно используете принцип нулевых накладных расходов в этом контексте. Наконец, предоставленный вами пример оптимизации не работает, потому что в этом примере устранение подвыражения может выполняться независимо от порядка вычисления. Я считаю этот ответ совершенно неправильным. - person Hadi Brais; 11.05.2016

Это не так. По крайней мере, сегодня нет. Может быть, это было в прошлом.

Предложение для C++17 предлагает определить порядок оценки влево-вправо для вызовов функций, operator<< и так далее.

Как описано в разделе 7 этого документа, это предложение было проверено путем компиляции ядра Windows NT, и оно фактически привело к увеличению скорости в некоторых тестах. Комментарий авторов:

Стоит отметить, что эти результаты относятся к наихудшему сценарию, когда оптимизаторы еще не были обновлены, чтобы знать и использовать преимущества новых правил оценки, и они слепо вынуждены оценивать вызовы функций слева направо.

предполагает, что есть возможности для дальнейшего повышения скорости.

person M.M    schedule 04.05.2016

Порядок оценки связан со способом передачи аргументов. Если для передачи аргументов используется стек, оценка справа налево помогает повысить производительность, поскольку именно так аргументы помещаются в стек.

Например, со следующим кодом:

void foo(bar(), baz());

Предполагая, что соглашение о вызовах - это «передача аргументов через стек», соглашение о вызовах C требует, чтобы аргументы помещались в стек, начиная с последнего, чтобы, когда вызываемая функция считывала его, она сначала извлекала первый аргумент и могла поддерживать функции с переменным числом аргументов. . Если бы порядок оценки был слева направо, результат bar() должен был бы быть сохранен во временном файле, затем вызывался бы baz(), его результат помещался в память, а затем выполнялся временный толчок. Однако вычисление справа налево позволяет компилятору избежать временных ошибок.

Если аргументы передаются через регистры, порядок оценки не имеет большого значения.

person SergeyA    schedule 04.05.2016
comment
Это зависит от компилятора. См. melpon.org/wandbox/permlink/P1ViEE6lb1x6rZnI и cpp.sh/8yya наблюдайте за выводом. - person Destructor; 04.05.2016
comment
@Destructor: Точно. - person Lightness Races in Orbit; 04.05.2016
comment
@SergeyA: Вы сначала сказали, что это не зависит от компилятора. - person Destructor; 04.05.2016
comment
@Destructor: Не будь грубым. И нет, это не неправильно. Порядок оценки не указан, поскольку оптимальный порядок зависит от компилятора. Сергей не очень ясно выразился, конечно, но он привел один пример возможного подхода. - person Lightness Races in Orbit; 04.05.2016
comment
@LightnessRacesinOrbit: как насчет переносимости кода из-за этого? - person Destructor; 04.05.2016
comment
@Destructor Это не совсем переносимо. Если вы используете неуказанное или неопределенное поведение, вы не можете гарантировать такое поведение. По крайней мере, с unspecified вы можете проверить реализацию, но зачем ее использовать. - person NathanOliver; 04.05.2016
comment
@Destructor: код не является переносимым, что зависит от порядка оценки. Если вы хотите иметь плохо написанные функции, которые зависят от глобального состояния и хотите обеспечить соблюдение порядка, сначала оцените их в отдельном выражении (назначьте ссылкам). - person Martin York; 04.05.2016
comment
Ничего не происходит с переносимостью кода, потому что порядок оценки не указан. Если бы он был указан, пострадала бы переносимость кода, поскольку передача фиксированных аргументов могла бы снизить производительность. Написание неопределенного кода было и всегда будет непереносимым даже на одном и том же компиляторе. - person SergeyA; 04.05.2016
comment
@LokiAstari: но порядок строго указан в других языках, таких как Java, C#, D и т. д. - person Destructor; 04.05.2016
comment
@Destructor: Конечно. Это разные языки, и они сделали разный выбор. - person Martin York; 04.05.2016
comment
@Destructor, вы можете использовать эти языки. Они не заботятся о производительности низкого уровня. - person SergeyA; 04.05.2016
comment
@Destructor C++ пытается позволить компиляторам выполнять как можно больше оптимизации. Один из способов — не указывать порядок оценки. Если бы они придерживались одного пути, то они могли бы оставить производительность на столе, а производительность — большая часть C++. Если вам нужен определенный заказ, вы можете вручную написать код для получения этого поведения, но теперь вы платите за этот оценочный заказ только тогда, когда он вам нужен. В большинстве случаев, когда это не нужно, вам не нужно платить за это. - person NathanOliver; 04.05.2016
comment
@SergeyA: я бы также добавил раздел о том, что порядок оценки подвыражений может быть оптимизирован в определенных ситуациях, поэтому порядок оценки выражения может влиять на порядок оценки параметров. - person Martin York; 04.05.2016
comment
@LokiAstari, боюсь, я недостаточно знаком с темой - может быть, вы можете добавить эту часть в качестве отдельного ответа? Или вы можете отредактировать мой ответ с конкретными деталями и примером для всеобщего блага. - person SergeyA; 04.05.2016
comment
Этот прирост производительности также будет зависеть от того, что push regname является единственным способом записи стека. Если вы пишете стек, индексируя например esp или ebp, то это не проблема. - person M.M; 04.05.2016
comment
@М.М. согласен полностью. Насколько я могу судить, компиляторы все еще склонны продвигаться вперед. - person SergeyA; 04.05.2016

Первоначальная причина того, что в стандартах C и C++ не указан порядок оценки аргументов функций, заключается в том, чтобы предоставить больше возможностей оптимизации для компилятора. К сожалению, это обоснование не было подкреплено обширными экспериментами во время первоначальной разработки этих языков. Но это имело смысл.

Этот вопрос поднимался в последние несколько лет. См. эту запись в блоге Herb. Саттер и не забудьте просмотреть комментарии.

В предложении P0145R1 рекомендуется указать порядок оценки аргументов функции и других операторов. В нем говорится:

Порядок вычисления выражений, как он в настоящее время указан в стандарте, подрывает советы, популярные идиомы программирования или относительную безопасность средств стандартной библиотеки. Ловушки не только для новичков или небрежного программиста. Они влияют на всех нас без разбора, даже если мы знаем правила.

Дополнительную информацию о том, как это влияет на возможности оптимизации, можно найти в этом документе.

В последние несколько месяцев велась очень обширная дискуссия о том, как это изменение языка влияет на оптимизацию, совместимость и переносимость. Тема начинается здесь и продолжается здесь. Вы можете найти там множество примеров.

person Hadi Brais    schedule 11.05.2016