О порядке ввода параметров
Если функция / метод содержит много входных параметров, имеет ли это значение, если передача выполняется в разных порядках? Если да, то в каких аспектах (читабельность, эффективность, ...)? Мне более интересно, как я должен делать для своих собственных функций / методов?
Мне кажется, что:
Параметры, передаваемые по ссылкам / указателям, часто предшествуют параметрам, передаваемым по значениям. Например:
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;
Они функционально эквивалентны (достигают той же цели) независимо от того, в каком порядке они проходят.
4 ответа
Есть несколько причин, по которым это может иметь значение - перечислены ниже. Сам по себе стандарт C++ не требует каких-либо конкретных действий в этом пространстве, поэтому нет переносимого способа рассуждать о влиянии на производительность, и даже если что-то заметно (немного) быстрее в одном исполняемом файле, в любом месте изменения программы или компилятора варианты или версия, может удалить или даже отменить предыдущее преимущество. На практике очень редко можно услышать, как люди говорят о том, что упорядочение параметров имеет какое-либо значение для их настройки производительности. Если вас это действительно волнует, лучше всего проверить вывод собственного кода компилятора и / или тестовый результат.
Исключения
Порядок вычисления выражений, передаваемых в параметры функции, не определен, и вполне возможно, что на него могут повлиять изменения порядка их появления в исходном коде, при этом некоторые комбинации лучше работают в конвейере выполнения ЦП или выдают исключение ранее. что замыкает какой-то другой параметр подготовки. Это может быть существенным фактором производительности, если некоторые параметры являются временными объектами (например, результатами выражений), которые дорого распределять / конструировать и уничтожать / освобождать. Опять же, любое изменение в программе может удалить или отменить преимущество или штраф, наблюдаемый ранее, поэтому, если вы заботитесь об этом, вы должны создать именованный временный параметр для параметров, которые вы хотите оценить в первую очередь, перед выполнением вызова функции.
Регистры против кеша (стековая память)
Некоторые параметры могут быть переданы в регистрах, в то время как другие помещаются в стек - что фактически означает ввод по крайней мере самого быстрого из кэшей ЦП и подразумевает, что их обработка может быть медленнее.
Если функция все равно получает доступ ко всем параметрам, и выбор между помещением параметра X в регистр и Y в стек или наоборот, не имеет большого значения, как они передаются, но учитывая, что функция может иметь условия влияет на то, какие переменные фактически используются (если операторы, переключатели, циклы, которые могут или не могут быть введены, ранние возвраты или разрывы и т. д.), потенциально быстрее, если переменная, которая на самом деле не нужна, была в стеке, а переменная, которая была нужна, находилась в регистр.
См. http://en.wikipedia.org/wiki/X86_calling_conventions для получения справочной информации и информации о соглашениях о вызовах.
Выравнивание и заполнение
Теоретически на производительность могут повлиять мелочи соглашений о передаче параметров: параметры могут нуждаться в особом выравнивании для любого - или, возможно, только для полноскоростного - доступа к стеку, и компилятор может предпочесть заполнить, а не переупорядочивать значения, которые он выдвигает - это Трудно представить, что это значимо, если только данные для параметров не соответствуют масштабу размеров страниц кеша
Неэффективные факторы
Некоторые из упомянутых вами факторов могут быть весьма важными - например, я склонен сначала указывать любые неконстантные указатели и ссылки и называть функцию load_xxx, поэтому у меня есть согласованное ожидание того, какие параметры могут быть изменены и какой порядок передать их. Там нет особо доминирующей конвенции, хотя.
Строго говоря, это не имеет значения - параметры помещаются в стек, а функция получает к ним доступ, каким-то образом извлекая их из стека.
Однако большинство компиляторов C / C++ позволяют указывать альтернативные соглашения о вызовах. Например, Visual C++ поддерживает соглашение __fastcall, в котором хранятся первые 2 параметра в регистрах ECX и EDX, что (теоретически) должно дать вам повышение производительности в нужных условиях.
Там также __thiscall, который хранит this
указатель в регистре ECX. Если вы делаете C++, то это может быть полезно.
Здесь есть несколько ответов, в которых упоминаются соглашения о вызовах. Они не имеют никакого отношения к вашему вопросу: независимо от того, какое соглашение о вызовах вы используете, порядок, в котором вы объявляете параметры, не имеет значения. Неважно, какие параметры передаются через регистры, а какие передаются по стеку, при условии, что одинаковое количество параметров передается через регистры и одинаковое количество параметров передается по стеку. Обратите внимание, что параметры, размер которых больше размера собственной архитектуры (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, если я передам вектор (100) и int, будет лучше, если вектор (100) будет первым, т.е. используйте регистры для загрузки большего типа данных. Правильно?
Нет. Как я уже говорил, размер данных не имеет значения. Давайте поговорим о 32-битной архитектуре (то же самое обсуждение справедливо для любой архитектуры 16-битной, 64-битной и т. Д.). Давайте проанализируем 3 случая, которые мы можем иметь в отношении размера параметров по отношению к собственному размеру архитектуры.
- Одинаковый размер: 4-байтовые параметры. Здесь нечего говорить.
- Меньший размер: будет использован 4-байтовый регистр или 4-байтовые будут выделены в стеке. Так что ничего интересного здесь тоже нет.
- Больший размер: (например, структура со многими полями или статический массив). Независимо от того, какой метод выбран для передачи этого аргумента, эти данные находятся в памяти, и передается указатель (размер 4 байта) на эти данные. Снова у нас есть 4-байтовый регистр или 4-байтовые в стеке.
Не имеет значения размер параметров.
Редактировать 3
Как объяснил @TonyD, порядок имеет значение, если вы не получаете доступ ко всем параметрам. Смотрите его ответ.
Я как-то нашел несколько связанных страниц.
https://google.github.io/styleguide/cppguide.html
Итак, сначала стиль Google C++ на самом деле не отвечает на вопрос, поскольку он не отвечает фактическому порядку в пределах входных или выходных параметров.
Другая страница в основном предполагает, что параметры заказа в некотором смысле просты для понимания и использования.
Для удобства чтения я лично предпочитаю заказывать параметры на основе алфавитного порядка. Но вы также можете поработать над некоторой стратегией, чтобы названия параметров были упорядочены так, чтобы их было легко понять и использовать.