О порядке ввода параметров
Для функции /method содержит много входных параметров, имеет ли значение значение, если передается в разных порядках? Если да, то в каких аспектах (читаемость, эффективность,...)? Мне больше любопытно, как мне делать свои собственные функции/методы?
Мне кажется, что
-
Параметры, проходящие по ссылкам/указателям, часто бывают перед передачей параметров по значениям. Например:
void* memset( void* dest, int ch, std::size_t count );
-
Параметры назначения часто появляются перед параметрами источника. Например:
void* memcpy( void* dest, const void* src, std::size_t count );
-
За исключением некоторых жестких ограничений, то есть параметры со значениями по умолчанию должны быть последними. Например:
size_type find( const basic_string& str, size_type pos = 0 ) const;
-
Они функционально эквивалентны (достигают той же цели) независимо от того, в каком порядке они проходят.
Ответы
Ответ 1
Есть несколько причин, которые могут иметь значение, перечисленные ниже. Сам стандарт С++ не предусматривает каких-либо конкретных действий в этом пространстве, поэтому нет никакого портативного способа рассуждать о влиянии производительности и даже если что-то явно (немного) быстрее в одном исполняемом файле, изменение в любом месте программы или компиляторе варианты или версия, могут удалить или даже отменить предыдущую выгоду. На практике очень редко слышать, как люди говорят о том, что порядок параметров имеет какое-либо значение при их настройке производительности. Если вы действительно заботитесь о том, что лучше всего изучить свой собственный выход компилятора и/или проверить результирующий код.
Исключения
Порядок оценки выражений, переданных параметрам функции, не указан, и вполне возможно, что на него могут повлиять изменения порядка, который они отображают в исходном коде, причем некоторые комбинации работают лучше в конвейере выполнения ЦП или повышают исключение ранее, которое замыкает некоторые другие параметры подготовки. Это может быть значительным коэффициентом производительности, если некоторые из параметров являются временными объектами (например, результатами выражений), которые дорого выделяются/строятся и разрушаются/освобождаются. Опять-таки, любое изменение в программе может удалить или отменить ранее полученное вознаграждение или штраф, поэтому, если вам это интересно, вы должны создать именованное временное значение для параметров, которые вы хотите оценить, прежде чем выполнять вызов функции.
Регистрирует vs cache (память стека)
Некоторые параметры могут быть переданы в регистры, а другие - в стек, что фактически означает ввод, по крайней мере, самого быстрого кэша ЦП, и подразумевает, что их обработка может быть медленнее.
Если функция все равно получает доступ ко всем параметрам, и выбор заключается в том, чтобы поставить параметр X в регистр и Y в стек или наоборот, не имеет большого значения, как они передаются, но с учетом функции могут иметь условия, влияющие на то, какие переменные фактически используются (если заявления, переключатели, петли, которые могут быть или не могут быть введены, ранние возвращения или разрывы и т.д.), это потенциально быстрее, если переменная, которая фактически не нужна, была в стеке, необходимо было в реестре.
Смотрите http://en.wikipedia.org/wiki/X86_calling_conventions для фона и информации о вызовах.
Выравнивание и дополнение
Производительность теоретически может быть затронута минутами соглашений о передаче параметров: параметрам может потребоваться конкретное выравнивание для любого - или, возможно, только полного - доступа в стеке, и компилятор может выбрать вариант, а не изменять порядок значений толкает - трудно представить, что важно, если данные для параметров не были в масштабе размеров страниц кеша
Факторы, не связанные с производительностью
Некоторые из других факторов, которые вы упомянули, могут быть весьма важными - например, я сначала ставил любые неконстантные указатели и ссылки и называю функцию load_xxx, поэтому у меня есть последовательное ожидание того, какие параметры могут быть изменены и который прикажет их передать. Там нет особо доминирующей конвенции.
Ответ 2
Строго говоря, это не имеет значения - параметры вставляются в стек и функция, получая их от них, отвлекая их из стека каким-либо образом.
Как бы то ни было, большинство компиляторов C/С++ позволяют вам указывать альтернативные соглашения о вызовах. Например, Visual С++ поддерживает соглашение __ fastcall, в котором хранятся первые 2 параметра в регистре ECX и EDX, которые (теоретически) должны дать вам улучшение производительности при правильных обстоятельствах.
Там также __ thiscall, который хранит указатель this
в регистре ECX. Если вы делаете С++, это может быть полезно.
Ответ 3
Здесь есть несколько ответов, в которых упоминаются соглашения о вызовах. Они не имеют никакого отношения к вашему вопросу: независимо от того, какую конвенцию вы используете, порядок, в котором вы объявляете параметры, не имеет значения. Не имеет значения, какие параметры передаются регистрами и которые передаются стеком, при условии, что одинаковое количество параметров передается регистрами и одинаковое количество параметров передается стеком. Обратите внимание, что параметры, размер которых превышает размер собственной архитектуры (4 байта для 32-разрядных и 8-байтовых для 64-разрядных), передаются по адресу, поэтому они передаются с той же скоростью, что и данные меньшего размера.
Возьмем пример:
У вас есть функция с 6 параметрами. И у вас есть соглашение о вызове, давайте вызывать его CA, который передает один параметр по регистру, а остальное (5 в этом случае) по стеку и второе соглашение о вызове, позволяет называть его CB, который передает 4 параметра регистрами, а остальные (в этом случае 2) по стеклу.
Теперь, конечно, CA будет быстрее, чем CB, но это не имеет ничего общего с порядком, объявленным параметрами. Для CA это будет так же быстро, независимо от того, какой параметр вы объявляете первым (по регистру) и который вы объявляете 2-й, 3-й (шестой) (стек), а для CB он будет таким же быстрым, независимо от того, какие 4 аргумента вы объявляете для регистров и которые вы объявляете как последние 2 параметра.
Теперь, касаясь вашего вопроса:
Единственное правило, которое является обязательным, заключается в том, что необязательные параметры должны быть объявлены последними. Без необязательного параметра может следовать необязательный параметр.
Кроме этого, вы можете использовать любой желаемый порядок, и единственный сильный совет, который я могу вам дать, - быть последовательным. Выберите модель и придерживайтесь ее.
Некоторые рекомендации, которые вы могли бы рассмотреть:
- назначение приходит до источника. Это должно быть близко к
destination = source
.
- размер буфера поступает после буфера:
f(char * s, unsigned size)
- параметры ввода сначала, выходные параметры последние (это противоречит первому, который я дал вам)
Но нет никакого "неправильного" или "правильного" или даже универсального общепринятого правила для порядка параметров. Выбирайте что-то и будьте последовательны.
Edit
Я подумал о "неправильном" способе заказа параметров: по алфавиту:).
Изменить 2
Например, как для CA, если я передаю вектор (100) и int, будет лучше, если сначала будет вектор (100), т.е. использовать регистры для загрузки большего типа данных. Правильно?
Нет. Как я уже упоминал, не имеет значения размер данных. Давайте поговорим о 32-битной архитектуре (одно и то же обсуждение подходит для любой архитектуры 16-разрядной, 64-разрядной и т.д.). Проанализируем 3 случая, которые мы можем иметь относительно размера параметров в зависимости от собственного размера архитектуры.
- Тот же размер: 4 байта. Нечего говорить здесь.
- Меньший размер: будет использоваться 4-байтовый регистр или 4 байта будут выделены в стеке. Так что ничего интересного здесь тоже.
- Больший размер: (например, структура со многими полями или статический массив). Независимо от того, какой метод выбран для передачи этого аргумента, эти данные хранятся в памяти, а передаваемый - это указатель (размер 4 байта) на эти данные. Снова у нас есть 4-байтовый регистр или 4 байта в стеке.
Не имеет значения размер параметров.
Изменить 3
Как объяснил @TonyD, порядок имеет значение, если вы не имеете доступа ко всем параметрам. См. Его ответ.
Ответ 4
Я как-то нашел несколько связанных страниц.
https://softwareengineering.stackexchange.com/info/101346/what-is-best-practice-on-ordering-parameters-in-a-function
https://google.github.io/styleguide/cppguide.html#Function_Parameter_Ordering
Итак, первый стиль Google С++ не отвечает на вопрос, так как он не может ответить на фактический порядок во входных параметрах или выходных параметрах.
Другая страница в принципе предполагает, что параметры порядка в определенном смысле легко понять и использовать.
Для удобства чтения я лично предпочитаю заказывать параметр на основе порядка алфавита. Но вы также можете поработать над какой-то стратегией, чтобы назвать параметры, которые должны быть хорошо упорядочены, чтобы их можно было легко понять и использовать.