Подтвердить что ты не робот

Почему бы не отметить все встроенные?

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

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

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

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

Кто-нибудь пробовал это с помощью реального приложения? Увеличилась ли производительность? уменьшить?!?

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

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

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

4b9b3361

Ответ 1

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

Собственно, Microsoft Visual С++ делает именно это, когда вы используете переключатель /GL (Оптимизация всей программы), он фактически не компилируется все, до тех пор, пока компоновщик не запустится и не получит доступ ко всему коду. Другие компиляторы имеют схожие варианты.

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

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

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

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

Ответ 7

Это уже сделано в некоторых случаях. Он очень похож на идею создания единиц объединения, а преимущества и недостатки - это не то, что вы опускаете:

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

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

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

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

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

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

Ответ 8

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

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

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

Ответ 9

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

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

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

Ответ 10

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

Ответ 11

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

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

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