Накладные расходы в неиспользуемом коде

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

Скажем, например, у вас есть журнал отладки, а затем вы даете большинству своих объектов функцию ToString(), которая используется в журналах отладки.

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

Или они просто немного увеличивают размер исполняемого файла и в остальном не влияют на производительность? например не влияет на скорость? Или компилятор или компоновщик, возможно, даже удаляет функции, если они не используются? Если компилятор или компоновщик не удалит код, что, если бы функции ToString() были определены встроенными? Предположительно, он попытается встроить код, и, поскольку функция никогда не вызывается, он исчезнет?

Я предполагаю, что каждая функция должна быть сохранена в статической библиотеке, но после компиляции в исполняемый файл наверняка многие вещи просто игнорируются компоновщиком?

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

Спасибо


person Cookie    schedule 26.05.2011    source источник
comment
Это может зависеть от компилятора и от того, какие оптимизации он использует.   -  person soandos    schedule 26.05.2011
comment
Преждевременная оптимизация — корень всех зол — Дональд Кнут   -  person Maurits Rijk    schedule 26.05.2011
comment
Я так устал от мантры преждевременной оптимизации. Да, спасибо за вашу помощь. Также: Понимание того, что вы делаете, может помочь вам сделать это хорошо. Или вы не согласны? Конечно, понимание компиляторов и компоновщиков неплохо. Но спасибо за ваш вклад.   -  person Cookie    schedule 26.05.2011
comment
@Maurits: мне не нравятся неполные кавычки. Не забывайте Говорить в 97 % случаев перед этим.   -  person Matthieu M.    schedule 26.05.2011
comment
Полная цитата выглядит так: «Мы должны забыть о малой эффективности, скажем, примерно в 97% случаев: преждевременная оптимизация — корень всех зол».   -  person Maurits Rijk    schedule 26.05.2011
comment
Журналы отладки довольно известны своими потенциальными накладными расходами. Простые реализации создают большие строки, синхронизируют потоки, чтобы предотвратить смешивание сообщений журнала, а затем отбрасывают все. Ложная синхронизация особенно опасна, так как может даже привести к взаимоблокировкам. По этим причинам я всегда исключаю ведение журнала с помощью макросов.   -  person MSalters    schedule 26.05.2011
comment
«Если не отлаживать, то выйти» в верхней части вызова регистратора — тривиальные накладные расходы. Глобальный параметр «отладка» может быть установлен параметром командной строки или аналогичным. Я согласен с другим вашим замечанием - строки журнала должны быть поставлены в очередь в поток регистратора - единственный поток, который касается файла журнала - и вызов журнала возвращается "немедленно". Единственная синхронизация должна быть вокруг нажатия очереди регистратора - красиво и коротко.   -  person Martin James    schedule 26.05.2011


Ответы (4)


Это зависит от компилятора и, наверное, от уровня оптимизации.

G++ и MSVC++ удаляют неиспользуемые встроенные функции, но сохраняют неиспользуемые невстроенные функции. Например, в обычной программе вы используете лишь небольшую часть STL. Все неиспользуемые функции удаляются, поскольку они определены как встроенные.

С другой стороны, GCC сохраняет все функции, даже неиспользуемые встроенные.

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

person user703016    schedule 26.05.2011
comment
Просто повторите ваше последнее предложение: если оно не определено как встроенное. Это должно остановить ваш компилятор от хмурого взгляда, даже если бы он не был определен встроенным. - person Cookie; 26.05.2011

1. О компиляторах и компоновщиках

Это действительно зависит от того, как вы создаете исполняемый файл.

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

Однако, если вы свяжетесь динамически, они будут там, потому что, что касается библиотеки, они экспортируются и, следовательно, используются.

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

Наконец, они, вероятно, представляют лишь маргинальную часть вашей программы.

2. Как решить проблему?

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

Лично я бы не стал заморачиваться... тем более, что я тоже вхожу в Release (а как еще отследить производственные проблемы?).

Решение может состоять в том, чтобы определить вызывающие нарушение функции в отдельном файле и не связывать их в Release. Примечание: я не думаю, что это работает для виртуальных функций, так как они по крайней мере используются в vtable

person Matthieu M.    schedule 26.05.2011

Компоновщики удаляют повторяющиеся функции и данные, на которые нет ссылок (компоновщик Microsoft предлагает переключатели /OPF:REF и /OPT:ICF для настройки этих параметров).

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

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

Еще одно преимущество #ifdef заключается в том, что он переносим и не зависит от конкретной системы компиляции :-/

person Alexander Gessler    schedule 26.05.2011
comment
Не могли бы вы тогда сказать, что предпочтительнее определять такие функции отладки tostring как встроенные? - person Cookie; 26.05.2011
comment
Поскольку компилятор всегда свободен встраивать или не встраивать функцию, я обычно использую inline только для одной цели: иметь возможность иметь определения функций в общих файлах заголовков. Поскольку это неявно для функций-членов, определенных в телах классов, я бы не стал указывать inline. Но учтите, что это всего лишь мой личный подход к inline. - person Alexander Gessler; 26.05.2011

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

person James Kanze    schedule 26.05.2011