Вопрос

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

Чтобы снизить уровень ошибочных ответов, убедитесь, что вы понимаете, что inline ключевое слово на самом деле означает.Вот хорошее описание, встроенный против статического против внешнего.

Итак, мой вопрос: почему бы не отметить каждое определение функции inline?т.е. в идеале единственной единицей компиляции была бы main.cpp.Или, возможно, еще несколько для функций, которые не могут быть определены в заголовочном файле (идиома pimpl и т.д.).

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

Кто-нибудь пробовал это с реальным приложением?Повысилась ли производительность?уменьшаться?!?

Каковы недостатки маркировки всех определений функций inline?

  • Компиляция может быть медленнее и потреблять гораздо больше памяти.
  • Итеративные сборки нарушены, все приложение необходимо будет перестраивать после каждого изменения.
  • Время соединения может быть астрономическим

Все эти недостатки сказываются только на разработчике.Каковы недостатки времени выполнения?

Это было полезно?

Решение

Вы действительно имели в виду #include все? Это дало бы вам только один модуль и позволил бы оптимизатору увидеть всю программу одновременно.

На самом деле, Visual C ++ Microsoft делает именно это, когда вы используете /GL (Вся оптимизация программы) Переключатель, на самом деле он ничего не компилируется, пока линкера не запустится и не получит доступ ко всему коду. Другие компиляторы имеют аналогичные варианты.

Другие советы

SQLite использует эту идею. Во время разработки он использует традиционную структуру источника. Но для фактического использования есть один огромный файл C (112K строки). Они делают это для максимальной оптимизации. Претендовать на 5-10% повышение производительности

http://www.sqlite.org/amalgamation.html

Мы (и некоторые другие игровые компании) попробовали это, сделав один Uber- .cpp, что #includeЭд всех остальных; Это известная техника. В нашем случае это, похоже, не сильно повлияло на время выполнения, но упомянутые вами недостатки с компиляцией оказались совершенно карели. С компиляцией полчаса после каждого изменения становится невозможным эффективно. (И это связано с приложением, разбитым в более чем дюжину различных библиотек.)

Мы попытались сделать другую конфигурацию, чтобы у нас было несколько .OBJ во время отладки, а затем иметь UBER-CPP только в сборке релизов-OPT, но затем столкнулся с проблемой компилятора просто заканчивая память. Для достаточно большого приложения инструменты просто не собираются собирать многомиллионную линию CPP -файла.

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

Интересный вопрос! Вы, безусловно, правы, что все перечисленные недостатки специфичны для разработчика. Я бы предположил, однако, что девелопер в неблагоприятном положении гораздо реже производит качественный продукт. Не может быть никаких недостатков времени выполнения, но представьте, как неохотно разработчик будет вносить небольшие изменения, если каждому компиляции требуются часы (или даже дни) для завершения.

Я бы посмотрел на это с угла «преждевременной оптимизации»: модульный код в нескольких файлах облегчает жизнь для программиста, поэтому существует очевидное преимущество в этом. Только если конкретное приложение оказывается слишком медленным, и можно показать, что внедрение всего делает измеренное улучшение, я бы даже даже рассмотреть возможность неудобства разработчиков. Даже тогда, это было бы после того, как большая часть разработки будет сделана (чтобы ее можно было измерить) и, вероятно, будет сделано только для производственных сборки.

Это полусвязано, но обратите внимание, что Visual C ++ обладает способностью делать оптимизацию кросс-модулей, включая встроенные по модулям. Видеть http://msdn.microsoft.com/en-us/library/0zza0de8%28vs.80%29.aspx Для получения информации.

Чтобы добавить ответ на ваш первоначальный вопрос, я не думаю, что будет недостатком во время выполнения, предполагая, что оптимизатор был достаточно умным (следовательно, почему он был добавлен в качестве опции оптимизации в Visual Studio). Просто используйте компилятор достаточно умный, чтобы сделать это автоматически, не создавая все проблемы, которые вы упоминаете. :)

Маленькая выгодаНа хорошем компиляторе для современной платформы, inline повлияет только на очень мало функций. Это просто намекать Для компилятора современные компиляторы довольно хороши в принятии этого решения самостоятельно, и накладные расходы вызова функционального вызова стали довольно небольшими (часто основным преимуществом внедрения является не уменьшение накладных расходов, а открытие дальнейшей оптимизации).

Время компиляции Однако, поскольку inline также меняет семантику, вам придется #include Все в одну огромную компиляцию. Этот обычно значительно увеличивает время компиляции, что является убийцей крупных проектов.

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

Тем не менее, некоторые проекты могут и получать прибыль от «встроенного всего». Это дает вам тот же эффект, что и оптимизация времени ссылки, по крайней мере, если ваш компилятор не слепо следит за inline.

В некоторых случаях это уже делается.Это очень похоже на идею единство строит, и преимущества и недостатки не отличаются от того, что вы описываете:

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

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

Однако, как всегда, когда речь идет о производительности, все зависит от обстоятельств.Это неплохая идея, но она также не применима повсеместно.

Что касается рабочего времени, то у вас есть в основном два способа его оптимизации:

  • сведите к минимуму количество единиц перевода (чтобы ваши заголовки были включены в меньшее количество мест), или
  • минимизируйте объем кода в заголовках (чтобы снизить стоимость включения заголовка в несколько единиц перевода).

Код на C обычно использует второй вариант, в значительной степени доведенный до крайности:в заголовках почти ничего, кроме прямых объявлений и макросов, не хранится.C ++ часто находится примерно посередине, где вы получаете наихудшее возможное общее время сборки (но PCH и / или инкрементные сборки могут снова сократить его на некоторое время), но, двигаясь дальше в другом направлении, минимизация количества единиц перевода действительно может творить чудеса с общим временем сборки.

Это в значительной степени философия позади Вся оптимизация программы и генерация кода времени ссылки (LTCG): возможности оптимизации лучше всего подходят для глобальных знаний.

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

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

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

Большие функции В общей стоимости при оптимизации существует баланс между накладными расходами локальных переменных и количеством кода в функции. Поддержание количества переменных в функции (оба передаваемого, локального и глобального) в течение количества одноразовых переменных для платформы приводит к тому, что большинство, которые могут оставаться в регистрах, и не должны быть выселены в ОЗУ, а также в стеке Кадр не требуется (в зависимости от цели), поэтому вызов функции вызов накладных расходов заметно уменьшается. Трудно делать в реальных приложениях все время, но альтернатива небольшое количество больших функций с большим количеством локальных переменных, которые код собирается потратить значительное количество времени на выселение и нагрузку с переменными в/из ОЗУ (зависит от цель).

Попробуйте LLVM, он может оптимизировать по всей программе, а не только функция по функции. Выпуск 27 догнал оптимизатора GCC, по крайней мере, для испытания или двух, я не прошел исчерпывающее тестирование производительности. И 28 вышел, поэтому я предполагаю, что это лучше. Даже с несколькими файлами количество комбинаций настройки настройки слишком много, чтобы возиться. Я считаю, что лучше не оптимизировать вообще, пока у вас не будет всей программы в один файл, а затем выполните оптимизацию, предоставив оптимизатору всю программу для работы, в основном то, что вы пытаетесь сделать с инлин, но без багажа.

Предполагать foo() а также bar() Оба называют некоторых helper(). Анкет Если все находится в одном компиляционном блоке, компилятор может выбрать не встроить helper(), чтобы уменьшить общий размер инструкции. Это вызывает foo() Чтобы сделать неинлизованный вызов функции helper().

Компилятор не знает, что наносекундное улучшение времени выполнения foo() Добавляет 100 долларов в день в вашу прибыль в ожидании. Он не знает, что повышение производительности или деградация чего -либо за пределами foo() не влияет на вашу прибыль.

Только вы, как программист, знаете эти вещи (после тщательного профилирования и анализа, конечно). Решение не вступать в линию bar() это способ рассказать компилятору, что вы знаете.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top