О порядке ввода параметров

Если функция / метод содержит много входных параметров, имеет ли это значение, если передача выполняется в разных порядках? Если да, то в каких аспектах (читабельность, эффективность, …)? Меня больше интересует, как мне поступить со своими собственными функциями / методами?

Мне кажется, что:

  1. Параметры, передаваемые по ссылкам / указателям, часто предшествуют параметрам, передаваемым по значениям. Например:

    void* memset( void* dest, int ch, std::size_t count );
    
  2. Параметры назначения часто предшествуют параметрам источника. Например:

    void* memcpy( void* dest, const void* src, std::size_t count );
    
  3. За исключением некоторых жестких ограничений, то есть параметры со значениями по умолчанию должны быть последними. Например:

    size_type find( const basic_string& str, size_type pos = 0 ) const;
    
  4. Они функционально эквивалентны (достигают той же цели) независимо от того, в каком порядке они проходят.

16

Решение

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

Исключения

Порядок вычисления выражений, передаваемых в параметры функции, не определен, и вполне возможно, что на него могут повлиять изменения порядка их появления в исходном коде, при этом некоторые комбинации лучше работают в конвейере выполнения ЦП или выдают исключение ранее. что замыкает какой-то другой параметр подготовки. Это может быть существенным фактором производительности, если некоторые параметры являются временными объектами (например, результатами выражений), которые дорого размещать / конструировать и уничтожать / освобождать. Опять же, любое изменение в программе может удалить или отменить преимущество или штраф, наблюдаемый ранее, поэтому, если вы заботитесь об этом, вы должны создать именованный временный параметр для параметров, которые вы хотите оценить, прежде чем выполнять вызов функции.

Регистры против кеша (стековая память)

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

Если функция все равно получает доступ ко всем параметрам, и выбор между помещением параметра X в регистр и Y в стек или наоборот, не имеет большого значения, как они передаются, но учитывая, что функция может иметь условия влияет на то, какие переменные фактически используются (если операторы, переключатели, циклы, которые могут или не могут быть введены, ранние возвраты или разрывы и т. д.), потенциально быстрее, если переменная, которая на самом деле не нужна, была в стеке, а переменная, которая была нужна, находилась в регистр.

Увидеть http://en.wikipedia.org/wiki/X86_calling_conventions для некоторого фона и информации о соглашениях о вызовах.

Выравнивание и заполнение

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

Неэффективные факторы

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

12

Другие решения

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

Однако большинство компиляторов C / C ++ позволяют указывать альтернативные соглашения о вызовах. Например, Visual C ++ поддерживает __fastcall соглашение, в котором первые два параметра хранятся в регистрах ECX и EDX, что (теоретически) должно дать вам повышение производительности при правильных обстоятельствах.

Есть также __thiscall который хранит this указатель в регистре ECX. Если вы делаете C ++, то это может быть полезно.

3

Здесь есть несколько ответов, в которых упоминаются соглашения о вызовах. Они не имеют никакого отношения к вашему вопросу: независимо от того, какое соглашение о вызовах вы используете, порядок, в котором вы объявляете параметры, не имеет значения. Неважно, какие параметры передаются через регистры, а какие передаются по стеку, при условии, что одинаковое количество параметров передается через регистры и одинаковое количество параметров передается по стеку. Обратите внимание, что параметры, размер которых больше размера собственной архитектуры (4 байта для 32-битной и 8-байтовой для 64-битной), передаются по адресу, поэтому они передаются с той же скоростью, что и данные меньшего размера ,

Давайте возьмем пример:

У вас есть функция с 6 параметрами. И у вас есть соглашение о вызовах, давайте назовем его CA, который передает один параметр регистром, а остальные (5 в нашем случае) по стеку, и второе соглашение о вызовах, давайте вызовем его CB, который передает 4 параметра регистрами, а остальные (в данном случае 2) по стеку.

Конечно, CA будет быстрее CB, но это никак не связано с порядком, в котором объявлены параметры. Для CA это будет так же быстро, независимо от того, какой параметр вы объявляете первым (по регистру), а какой — 2, 3, 6 (стек), а для CB — так же быстро, независимо от того, какие 4 аргумента вы объявляете для регистров. и который вы объявляете как последние 2 параметра.


Теперь по поводу вашего вопроса:

Единственное обязательное правило — необязательные параметры должны быть объявлены последними. Никакой необязательный параметр не может следовать за необязательным параметром.

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

Некоторые рекомендации, которые вы могли бы рассмотреть:

  • пункт назначения предшествует источнику. Это должно быть близко к destination = source,
  • размер буфера идет после буфера: f(char * s, unsigned size)
  • сначала входные параметры, последние выходные параметры (это противоречит первому, который я вам дал)

Но не существует «неправильных», «правильных» или даже общепринятых рекомендаций по порядку параметров. Выбери что-нибудь и будь последовательным.

редактировать

Я подумал о «неправильном» способе упорядочить ваши параметры: по алфавиту :).

Редактировать 2

Например, и для CA, если я передам vector (100) и int, будет лучше, если vector (100) будет первым, т. Е. Использовать регистры для загрузки большего типа данных. Правильно?

Нет. Как я уже говорил, размер данных не имеет значения. Давайте поговорим о 32-битной архитектуре (то же самое справедливо для любой архитектуры: 16-битной, 64-битной и т. Д.). Давайте проанализируем 3 случая, которые мы можем иметь в отношении размера параметров по отношению к собственному размеру архитектуры.

  • Одинаковый размер: 4-байтовые параметры. Здесь нечего говорить.
  • Меньший размер: будет использован 4-байтовый регистр или 4-байтовые будут выделены в стеке. Так что ничего интересного здесь тоже нет.
  • Больший размер: (например, структура со многими полями или статический массив). Независимо от того, какой метод выбран для передачи этого аргумента, эти данные находятся в памяти, и передается указатель (размер 4 байта) на эти данные. Снова у нас есть 4-байтовый регистр или 4-байтовые в стеке.

Не имеет значения размер параметров.

Редактировать 3

Как объяснил @TonyD, порядок имеет значение, если вы не получаете доступ ко всем параметрам. Смотрите его ответ.

3

Я как-то нашел несколько связанных страниц.

https://softwareengineering.stackexchange.com/questions/101346/what-is-best-practice-on-ordering-parameters-in-a-function

https://google.github.io/styleguide/cppguide.html#Function_Parameter_Ordering

Итак, сначала стиль Google C ++ на самом деле не отвечает на вопрос, так как он не отвечает фактическому порядку входных или выходных параметров.

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

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

1