Почему бы не пометить все в строке? - PullRequest
46 голосов
/ 22 октября 2010

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

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

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

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

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

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

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

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

Ответы [ 11 ]

24 голосов
/ 22 октября 2010

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

На самом деле, Microsoft Visual C ++ делает именно это, когда вы используете переключатель 1006 * (Оптимизация всей программы) , на самом деле он ничего не компилирует, пока компоновщик не запустится и не получит доступ ко всему коду. Другие компиляторы имеют аналогичные параметры.

15 голосов
/ 22 октября 2010

sqlite использует эту идею.В процессе разработки используется традиционная исходная структура.Но для реального использования есть один огромный c файл (112k строк).Они делают это для максимальной оптимизации.Заявить о повышении производительности на 5-10%

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

9 голосов
/ 23 октября 2010

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

Мы попытались создать другую конфигурацию, чтобы во время отладки мы имели несколько .objs, а затем использовали uber-CPP только в выпусках release-opt, но затем столкнулись с проблемой того, что компилятору просто не хватает памяти. Для достаточно большого приложения инструменты просто не подходят для компиляции многомиллионного файла cpp.

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

8 голосов
/ 22 октября 2010

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

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

7 голосов
/ 22 октября 2010

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

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

4 голосов
/ 24 октября 2010

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

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

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

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

3 голосов
/ 24 октября 2010

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

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

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

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

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

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

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

2 голосов
/ 23 октября 2010

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

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

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

2 голосов
/ 22 октября 2010

Это в значительной степени философия Оптимизация всей программы и Link Time Generation (LTCG): возможности оптимизации являются лучшими с глобальными знаниями.

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

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

1 голос
/ 05 января 2016

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

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

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

...