Много лет назад компиляторы C не были особенно умными. В качестве обходного пути K&R изобрел ключевое слово register , чтобы намекнуть компилятору, что, возможно, было бы неплохо сохранить эту переменную во внутреннем регистре. Они также сделали третичный оператор, чтобы помочь сгенерировать лучший код.
Со временем составители стали зрелыми. Они стали очень умными в том, что их анализ потока позволяет им принимать более правильные решения о том, какие значения хранить в регистрах, чем вы могли бы сделать. Ключевое слово register стало неважным.
FORTRAN может быть быстрее C для некоторых видов операций из-за проблем с псевдонимами . Теоретически при тщательном кодировании можно обойти это ограничение, чтобы оптимизатор мог генерировать более быстрый код.
Какие методы кодирования позволяют компилятору / оптимизатору генерировать более быстрый код?
- Мы будем признательны за определение платформы и компилятора, которые вы используете.
- Почему кажется, что техника работает?
- Приветствуется образец кода.
Вот связанный с этим вопрос
[Edit] Этот вопрос касается не общего процесса профилирования, а оптимизации. Предположим, что программа написана правильно, скомпилирована с полной оптимизацией, протестирована и запущена в производство. В вашем коде могут быть конструкции, которые не позволяют оптимизатору выполнять свою работу наилучшим образом. Что вы можете сделать для рефакторинга, который снимет эти запреты и позволит оптимизатору генерировать еще более быстрый код?
[Edit] Ссылка на смещение
источник
register
фактически сделал чувствительный к производительности код более переносимым за счет борьбы с плохими компиляторами.Ответы:
Запись в локальные переменные, а не в аргументы вывода! Это может быть огромным подспорьем для решения проблемы замедления наложения спектров. Например, если ваш код выглядит как
компилятор не знает, что foo1! = barOut, и поэтому должен перезагружать foo1 каждый раз в цикле. Он также не может читать foo2 [i], пока не завершится запись в barOut. Вы можете начать возиться с ограниченными указателями, но это так же эффективно (и намного понятнее):
Это звучит глупо, но компилятор может быть намного умнее, работая с локальной переменной, поскольку она не может перекрываться в памяти ни с одним из аргументов. Это может помочь вам избежать ужасного «load-hit-store» (упомянутого Фрэнсисом Бойвином в этой теме).
источник
Вот практика кодирования, которая поможет компилятору создавать быстрый код - любой язык, любая платформа, любой компилятор, любая проблема:
Вы не использовать любые трюки , какие силы, или даже поощрять, компилятор заложить переменные в памяти (включая кэш - память и регистры) , как вы думаете , лучше всего. Сначала напишите программу, которая будет правильной и удобной в обслуживании.
Затем профилируйте свой код.
Тогда и только тогда вы можете захотеть начать исследовать эффекты указания компилятору, как использовать память. Вносите по одному изменению за раз и измеряйте его влияние.
Ожидайте разочарования и действительно очень много работать над небольшими улучшениями производительности. Современные компиляторы для зрелых языков, таких как Fortran и C, очень и очень хороши. Если вы читали описание «уловки» для повышения производительности кода, имейте в виду, что авторы компилятора также читали об этом и, если это стоит сделать, вероятно, реализовали его. Вероятно, они изначально написали то, что вы читаете.
источник
&
против%
для степени двойки (редко, если вообще оптимизируется, но может иметь значительное влияние на производительность). Если вы читали трюк для повышения производительности, единственный способ узнать, работает ли он, - это внести изменения и измерить влияние. Никогда не предполагайте, что компилятор что-то оптимизирует за вас.n
gcc заменяет% n
на,& (n-1)
даже если оптимизация отключена . Это не совсем «редко, если вообще» ...Порядок, в котором вы просматриваете память, может иметь сильное влияние на производительность, и компиляторы не очень хороши в выяснении этого и исправлении. Если вы заботитесь о производительности, при написании кода необходимо учитывать проблемы расположения кеша. Например, двумерные массивы в C размещаются в строчном формате. Обход массивов в основном формате столбцов, как правило, приведет к увеличению количества промахов в кэше и сделает вашу программу более привязанной к памяти, чем к процессору:
источник
-floop-interchange
который будет менять местами внутренний и внешний цикл, если оптимизатор сочтет это выгодным.Общие оптимизации
Вот некоторые из моих любимых оптимизаций. Я фактически увеличил время выполнения и уменьшил размеры программ, используя их.
Объявите небольшие функции как
inline
или макросыКаждый вызов функции (или метода) влечет за собой накладные расходы, например, добавление переменных в стек. Некоторые функции также могут вызывать накладные расходы при возврате. Неэффективная функция или метод имеет меньше операторов в своем содержании, чем совокупные накладные расходы. Это хорошие кандидаты для встраивания, будь то
#define
макросы илиinline
функции. (Да, я знаю, чтоinline
это всего лишь предложение, но в данном случае я рассматриваю его как напоминание компилятору.)Удалить мертвый и избыточный код
Если код не используется или не влияет на результат программы, избавьтесь от него.
Упростите разработку алгоритмов
Однажды я удалил много ассемблерного кода и времени выполнения из программы, записав алгебраическое уравнение, которое она вычисляла, а затем упростил алгебраическое выражение. Реализация упрощенного алгебраического выражения занимала меньше места и времени, чем исходная функция.
Развертывание петли
У каждого цикла есть накладные расходы на проверку приращения и завершения. Чтобы получить оценку коэффициента производительности, подсчитайте количество инструкций в накладных расходах (минимум 3: приращение, проверка, переход к началу цикла) и разделите на количество инструкций внутри цикла. Чем меньше число, тем лучше.
Изменить: приведите пример развертывания цикла Перед:
После раскрутки:
Это преимущество дает дополнительное преимущество: выполняется больше операторов, прежде чем процессор должен перезагрузить кэш команд.
У меня были потрясающие результаты, когда я развернул цикл до 32 операторов. Это было одним из узких мест, поскольку программе приходилось вычислять контрольную сумму для файла размером 2 ГБ. Эта оптимизация в сочетании с чтением блоков повысила производительность с 1 часа до 5 минут. Развертывание цикла также обеспечивало отличную производительность на языке ассемблера, мой
memcpy
был намного быстрее, чем компиляторmemcpy
. - ТМСокращение
if
заявленийПроцессоры ненавидят переходы или переходы, так как они заставляют процессор перезагружать свою очередь инструкций.
Логическая арифметика ( отредактировано: применен формат кода к фрагменту кода, добавлен пример)
Преобразуйте
if
операторы в логические присваивания. Некоторые процессоры могут условно выполнять инструкции без ветвления:Короткое замыкание из Логических И оператора (
&&
) предотвращает выполнение тестов , еслиstatus
естьfalse
.Пример:
Распределение переменных факторов вне циклов
Если переменная создается «на лету» внутри цикла, переместите создание / выделение до цикла. В большинстве случаев переменную не нужно выделять на каждой итерации.
Факторные константные выражения вне циклов
Если вычисление или значение переменной не зависит от индекса цикла, переместите его за пределы (до) цикла.
Ввод / вывод в блоках
Чтение и запись данных большими порциями (блоками). Больше лучше. Например, чтение одного октета за раз менее эффективно, чем чтение 1024 октета за одно чтение.
Пример:
Эффективность этой техники можно продемонстрировать визуально. :-)
Не используйте
printf
семью для постоянных данныхПостоянные данные могут быть выведены с помощью блочной записи. Форматированная запись будет тратить время на сканирование текста для форматирования символов или обработки команд форматирования. См. Пример кода выше.
Отформатируйте в память, затем напишите
Отформатируйте в
char
массив, используя несколькоsprintf
, затем используйтеfwrite
. Это также позволяет разбить структуру данных на «постоянные разделы» и переменные разделы. Подумайте о слиянии писем .Объявить постоянный текст (строковые литералы) как
static const
Когда переменные объявляются без символа
static
, некоторые компиляторы могут выделять место в стеке и копировать данные из ПЗУ. Это две ненужные операции. Это можно исправить с помощьюstatic
префикса.Наконец, код, подобный компилятору
Иногда компилятор может оптимизировать несколько небольших операторов лучше, чем одну сложную версию. Также помогает написание кода, который помогает компилятору оптимизировать. Если я хочу, чтобы компилятор использовал специальные инструкции по передаче блоков, я напишу код, который выглядит так, как будто он должен использовать специальные инструкции.
источник
fprintf
форматирование в отдельный буфер затем выводит буфер. Оптимизированный (для использования памяти)fprintf
будет выводить весь неформатированный текст, затем форматировать и выводить и повторять до тех пор, пока не будет обработана вся строка формата, таким образом выполняя 1 выходной вызов для каждого типа вывода (форматированный или неформатированный). В других реализациях потребуется динамически выделять память для каждого вызова для хранения всей новой строки (что плохо для среды встроенных систем). Мое предложение уменьшает количество выходов.На самом деле оптимизатор не контролирует производительность вашей программы, а вы. Используйте соответствующие алгоритмы и структуры, а также профиль, профиль, профиль.
Тем не менее, вы не должны использовать внутренний цикл для небольшой функции из одного файла в другом файле, поскольку это останавливает ее встраивание.
По возможности избегайте использования адреса переменной. Запрос указателя не «бесплатный», так как это означает, что переменная должна храниться в памяти. Даже массив может храниться в регистрах, если вы избегаете указателей - это важно для векторизации.
Что приводит к следующему пункту, прочтите руководство по ^ # $ @ ! GCC может векторизовать простой код C, если вы добавите туда
__restrict__
и__attribute__( __aligned__ )
сюда. Если вам нужно что-то очень конкретное от оптимизатора, вам, возможно, придется быть конкретным.источник
A.c
встроенногоB.c
.В большинстве современных процессоров самым узким местом является память.
Псевдоним: Load-Hit-Store может быть разрушительным в замкнутом цикле. Если вы читаете одну ячейку памяти и записываете в другую и знаете, что они не пересекаются, аккуратное добавление ключевого слова alias к параметрам функции действительно может помочь компилятору сгенерировать более быстрый код. Однако, если области памяти перекрываются и вы использовали псевдоним, вас ждет хороший сеанс отладки неопределенного поведения!
Отсутствие кэша: не совсем уверен, как вы можете помочь компилятору, поскольку он в основном алгоритмический, но есть встроенные функции для предварительной выборки памяти.
Также не пытайтесь слишком много преобразовывать значения с плавающей запятой в int и наоборот, поскольку они используют разные регистры, и преобразование из одного типа в другой означает вызов фактической инструкции преобразования, запись значения в память и чтение его обратно в соответствующий набор регистров. ,
источник
Подавляющее большинство кода, который пишут люди, будет привязан к вводу-выводу (я считаю, что весь код, который я написал за деньги за последние 30 лет, был привязан к этому), так что действия оптимизатора для большинства людей будут академическими.
Однако я хотел бы напомнить людям, что для оптимизации кода вы должны сказать компилятору, чтобы он оптимизировал его - многие люди (включая меня, когда я забываю) публикуют здесь тесты C ++, которые бессмысленны без включенного оптимизатора.
источник
используйте в коде как можно больше константной корректности. Это позволяет компилятору намного лучше оптимизировать.
В этом документе есть множество других советов по оптимизации: Оптимизация CPP (хотя и немного старый документ)
Основные моменты:
источник
const
иrestrict
квалифицированного указателя, однако, не определен. Таким образом, в этом случае компилятор может оптимизировать иначе.const
наconst
ссылку илиconst
указатель на не-const
объекта хорошо определена. изменение фактическогоconst
объекта (т. е. того, который был объявленconst
изначально) - нет.Попытайтесь программировать, используя как можно больше статического одиночного присваивания. SSA - это то же самое, что и в большинстве функциональных языков программирования, и это то, во что большинство компиляторов конвертируют ваш код для оптимизации, потому что с ним легче работать. Таким образом обнаруживаются места, где компилятор может запутаться. Это также заставляет все, кроме худших, распределителей регистров работать так же хорошо, как и лучшие распределители регистров, и позволяет вам легче отлаживать, потому что вам почти никогда не придется задаваться вопросом, откуда переменная получила свое значение, поскольку было только одно место, где она была назначена.
Избегайте глобальных переменных.
При работе с данными по ссылке или указателю перетащите их в локальные переменные, выполните свою работу, а затем скопируйте их обратно. (если у вас нет веской причины не делать этого)
Используйте почти бесплатное сравнение с 0, которое большинство процессоров предоставляют вам при выполнении математических или логических операций. Вы почти всегда получаете флаг для == 0 и <0, из которого вы можете легко получить 3 условия:
почти всегда дешевле, чем проверка других констант.
Еще одна уловка - использовать вычитание, чтобы исключить одно сравнение при тестировании диапазона.
Это очень часто позволяет избежать скачка в языках, которые используют короткое замыкание для логических выражений, и позволяет компилятору не пытаться выяснить, как справиться с получением результата первого сравнения, выполняя второе и затем комбинируя их. Может показаться, что может потребоваться дополнительный регистр, но этого почти никогда не происходит. Часто foo все равно больше не нужен, а если вы его используете, rc еще не используется, поэтому он может пойти туда.
При использовании строковых функций в c (strcpy, memcpy, ...) помните, что они возвращают - адрес назначения! Часто можно улучшить код, «забыв» свою копию указателя на пункт назначения и просто забрав ее обратно из возврата этих функций.
Никогда не упускайте возможность вернуть то же самое, что вернула последняя вызванная функция. Компиляторы не очень хорошо улавливают:
Конечно, вы могли бы изменить логику этого, если бы имели только одну точку возврата.
(приемы вспомнил позже)
Объявление функций как статических по возможности всегда является хорошей идеей. Если компилятор может доказать самому себе, что он учел каждого вызывающего конкретную функцию, он может нарушить соглашения о вызовах этой функции во имя оптимизации. Компиляторы часто могут избегать перемещения параметров в регистры или позиции стека, в которых вызываемые функции обычно ожидают, что их параметры будут внутри (для этого необходимо отклоняться как в вызываемой функции, так и в местоположении всех вызывающих). Компилятор также часто может воспользоваться знанием того, какая память и регистры потребуются вызываемой функции, и избежать генерации кода для сохранения значений переменных, находящихся в регистрах или ячейках памяти, которые вызываемая функция не нарушает. Это особенно хорошо работает, когда есть несколько вызовов функции.
источник
Я написал оптимизирующий компилятор C, и вот несколько очень полезных вещей, которые следует учитывать:
Сделайте большинство функций статическими. Это позволяет межпроцедурному распространению констант и анализу псевдонимов выполнять свою работу, иначе компилятор должен предположить, что функция может быть вызвана извне единицы трансляции с полностью неизвестными значениями параметров. Если вы посмотрите на известные библиотеки с открытым исходным кодом, все они помечают функции как статические, за исключением тех, которые действительно должны быть внешними.
Если используются глобальные переменные, пометьте их как статические и постоянные, если это возможно. Если они инициализируются один раз (только для чтения), лучше использовать список инициализаторов, например static const int VAL [] = {1,2,3,4}, иначе компилятор может не обнаружить, что переменные на самом деле являются инициализированными константами и не сможет заменить нагрузки из переменной на константы.
НИКОГДА не используйте переход к внутренней части цикла, цикл больше не будет распознаваться большинством компиляторов, и ни одна из наиболее важных оптимизаций не будет применена.
Используйте параметры указателя только в случае необходимости и пометьте их как ограничивающие, если это возможно. Это очень помогает при анализе псевдонимов, потому что программист гарантирует отсутствие псевдонимов (межпроцедурный анализ псевдонимов обычно очень примитивен). Очень маленькие объекты структуры следует передавать по значению, а не по ссылке.
По возможности используйте массивы вместо указателей, особенно внутри циклов (a [i]). Массив обычно предлагает больше информации для анализа псевдонимов, и после некоторых оптимизаций в любом случае будет сгенерирован тот же код (поиск уменьшения силы цикла, если интересно). Это также увеличивает вероятность применения инвариантного к циклам движения кода.
Попробуйте поднять за пределы цикла вызовы больших функций или внешних функций, которые не имеют побочных эффектов (не зависят от текущей итерации цикла). Маленькие функции во многих случаях встроены или преобразованы во встроенные функции, которые легко поднять, но большие функции могут показаться компилятору с побочными эффектами, хотя на самом деле их нет. Побочные эффекты для внешних функций полностью неизвестны, за исключением некоторых функций из стандартной библиотеки, которые иногда моделируются некоторыми компиляторами, что делает возможным перемещение кода с инвариантным циклом.
При написании тестов с несколькими условиями ставьте наиболее вероятное первое. if (a || b || c) должно быть if (b || a || c), если b с большей вероятностью будет истинным, чем другие. Компиляторы обычно ничего не знают о возможных значениях условий и о том, какие ветви взяты больше (их можно узнать, используя информацию профиля, но немногие программисты используют ее).
Использование переключателя быстрее, чем выполнение теста, такого как if (a || b || ... || z). Сначала проверьте, делает ли ваш компилятор это автоматически, некоторые делают, и, тем не менее, будет удобнее иметь if .
источник
В случае встроенных систем и кода, написанного на C / C ++, я стараюсь максимально избегать динамического выделения памяти . Основная причина, по которой я это делаю, не обязательно связана с производительностью, но это практическое правило действительно влияет на производительность.
Алгоритмы, используемые для управления кучей, заведомо медленны на некоторых платформах (например, vxworks). Хуже того, время, необходимое для возврата из вызова malloc, сильно зависит от текущего состояния кучи. Следовательно, любая функция, вызывающая malloc, получит снижение производительности, которое нелегко учесть. Это снижение производительности может быть минимальным, если куча все еще чиста, но после того, как это устройство работает некоторое время, куча может стать фрагментированной. Вызовы будут занимать больше времени, и вы не сможете легко рассчитать, как со временем ухудшится производительность. Вы не можете произвести худшую оценку. Оптимизатор и в этом случае не может вам помочь. Что еще хуже, если куча станет слишком сильно фрагментированной, вызовы вообще начнут отказывать. Решение - использовать пулы памяти (например,glib ломтики ) вместо кучи. Вызовы распределения будут намного быстрее и детерминированы, если вы все сделаете правильно.
источник
Небольшой тупой совет, но он сэкономит вам микроскопическое количество скорости и кода.
Всегда передавайте аргументы функции в одном и том же порядке.
Если у вас есть f_1 (x, y, z), который вызывает f_2, объявите f_2 как f_2 (x, y, z). Не объявляйте его как f_2 (x, z, y).
Причина этого в том, что ABI платформы C / C ++ (соглашение о вызовах AKA) обещает передавать аргументы в определенных регистрах и местах стека. Когда аргументы уже находятся в правильных регистрах, их не нужно перемещать.
Читая дизассемблированный код, я видел нелепое перемешивание регистров, потому что люди не следовали этому правилу.
источник
Две техники кодирования, которые я не видел в приведенном выше списке:
Обойти компоновщик, написав код как уникальный источник
Хотя раздельная компиляция действительно хороша для времени компиляции, это очень плохо, когда вы говорите об оптимизации. В основном компилятор не может оптимизировать за пределами единицы компиляции, то есть зарезервированного домена компоновщика.
Но если вы хорошо спроектируете свою программу, вы также можете скомпилировать ее из уникального общего источника. То есть вместо компиляции unit1.c и unit2.c затем свяжите оба объекта, скомпилируйте all.c, который просто #include unit1.c и unit2.c. Таким образом, вы получите выгоду от всех оптимизаций компилятора.
Это очень похоже на написание программ только с заголовками на C ++ (а на C это еще проще).
Этот метод достаточно прост, если вы пишете свою программу, чтобы включить ее с самого начала, но вы также должны знать, что он изменяет часть семантики C, и вы можете столкнуться с некоторыми проблемами, такими как статические переменные или конфликты макросов. Для большинства программ достаточно легко преодолеть возникающие небольшие проблемы. Также имейте в виду, что компиляция в качестве уникального источника происходит намного медленнее и может потребовать огромного количества памяти (обычно это не проблема современных систем).
Используя эту простую технику, мне удалось сделать некоторые программы, которые я написал, в десять раз быстрее!
Как и ключевое слово register, этот трюк может скоро стать устаревшим. Оптимизация с помощью компоновщика начинает поддерживаться компиляторами gcc: Оптимизация времени компоновки .
Разделяйте атомарные задачи в циклах
Этот более сложный. Речь идет о взаимодействии между дизайном алгоритма и тем, как оптимизатор управляет кешем и распределением регистров. Довольно часто программам приходится перебирать какую-то структуру данных и для каждого элемента выполнять определенные действия. Часто выполняемые действия можно разделить между двумя логически независимыми задачами. В этом случае вы можете написать точно такую же программу с двумя циклами на одной границе, выполняя ровно одну задачу. В некоторых случаях написание этого способа может быть быстрее, чем уникальный цикл (детали более сложные, но объяснение может заключаться в том, что в простом случае задачи все переменные могут храниться в регистрах процессора, а в более сложном - это невозможно, а некоторые регистры должны быть записаны в память и считаны позже, а стоимость выше, чем дополнительное управление потоком).
Будьте осторожны с этим (производительность профиля с использованием этого трюка или без него), так как, как и при использовании регистра, он может дать меньшую производительность, чем улучшенные.
источник
Я действительно видел это в SQLite, и они утверждают, что это приводит к повышению производительности примерно на 5%: поместите весь свой код в один файл или используйте препроцессор, чтобы сделать эквивалент этого. Таким образом, оптимизатор получит доступ ко всей программе и сможет выполнять больше межпроцедурных оптимизаций.
источник
-O3
- он выбросил 22% от исходного размера моей программы. (Это не связано с процессором, поэтому мне особо нечего сказать о скорости.)Большинство современных компиляторов должны хорошо работать, ускоряя хвостовую рекурсию , потому что вызовы функций можно оптимизировать.
Пример:
Конечно, в этом примере нет проверки границ.
Позднее редактирование
Пока у меня нет прямого знания кода; кажется очевидным, что требования к использованию CTE на SQL Server были специально разработаны, чтобы его можно было оптимизировать с помощью конечной рекурсии.
источник
Не выполняйте одну и ту же работу снова и снова!
Обычный антипаттерн, который я вижу, выглядит следующим образом:
На самом деле компилятор должен вызывать все эти функции все время. Предполагая, что вы, программист, знаете, что агрегированный объект не меняется в ходе этих вызовов, ради всего святого ...
В случае одноэлементного геттера вызовы могут быть не слишком дорогостоящими, но это, безусловно, затраты (обычно «проверьте, был ли объект создан, если нет, создайте его, а затем верните его»). чем сложнее становится эта цепочка геттеров, тем больше у нас будет потраченного времени.
источник
Используйте максимально локальную область видимости для всех объявлений переменных.
Используйте по
const
возможностиНе используйте регистрацию, если вы не планируете профилировать как с ней, так и без нее.
Первые два из них, особенно первый, помогают оптимизатору анализировать код. Это особенно поможет ему сделать правильный выбор в отношении того, какие переменные хранить в регистрах.
Слепое использование ключевого слова register может помочь или повредить вашей оптимизации. Слишком сложно понять, что будет иметь значение, пока вы не посмотрите на результат сборки или профиль.
Есть и другие вещи, которые важны для повышения производительности кода; разработка ваших структур данных, например, для максимальной согласованности кеша. Но вопрос был об оптимизаторе.
источник
Выровняйте данные по естественным / естественным границам.
источник
Мне напомнили кое-что, с чем я столкнулся однажды, когда симптомом было просто то, что нам не хватало памяти, но в результате значительно повысилась производительность (а также огромное сокращение объема памяти).
Проблема в этом случае заключалась в том, что программное обеспечение, которое мы использовали, выделяло множество небольших ресурсов. Например, выделить четыре байта здесь, шесть байтов там и т. Д. Множество мелких объектов тоже работает в диапазоне 8-12 байтов. Проблема была не столько в том, что программе нужно было много мелочей, а в том, что она выделяла множество мелочей индивидуально, что увеличивало каждое выделение (на этой конкретной платформе) до 32 байтов.
Частью решения было собрать небольшой пул объектов в стиле Александреску, но расширить его, чтобы я мог выделять массивы как небольших объектов, так и отдельных элементов. Это также очень помогло в производительности, поскольку в кеш помещается больше элементов одновременно.
Другая часть решения заключалась в замене безудержного использования управляемых вручную членов char * строкой SSO (оптимизация малых строк). Минимальное выделение составляет 32 байта, я создал строковый класс, который имел встроенный 28-символьный буфер за символом *, поэтому 95% наших строк не нуждались в дополнительном распределении (а затем я вручную заменил почти все появления char * в этой библиотеке с этим новым классом, было весело или нет). Это также помогло тонне фрагментации памяти, которая затем увеличила локальность ссылок для других объектов, на которые указали, и аналогичным образом увеличилась производительность.
источник
Изящная техника, которую я узнал из комментария @MSalters к этому ответу, позволяет компиляторам выполнять удаление копий даже при возврате разных объектов в соответствии с некоторым условием:
источник
Если у вас есть небольшие функции, которые вы вызываете неоднократно, я в прошлом добивался больших успехов, помещая их в заголовки как «статические встроенные». Вызов функций на ix86 на удивление дорого обходится.
Повторная реализация рекурсивных функций нерекурсивным способом с использованием явного стека также может принести много пользы, но тогда вы действительно находитесь в области между временем разработки и выигрышем.
источник
Вот мой второй совет по оптимизации. Как и мой первый совет, это универсальный совет, а не язык или процессор.
Внимательно прочтите руководство к компилятору и поймите, о чем оно вам говорит. Используйте компилятор по максимуму.
Я согласен с одним или двумя другими респондентами, которые определили, что выбор правильного алгоритма имеет решающее значение для снижения производительности программы. Помимо этого, доходность (измеряемая в улучшении выполнения кода) от времени, которое вы вкладываете в использование компилятора, намного выше, чем доходность при настройке кода.
Да, авторы компиляторов не из расы гигантов кодирования, а компиляторы содержат ошибки, и то, что должно, согласно руководству и теории компиляторов, делать вещи быстрее, иногда делает работу медленнее. Вот почему вам нужно делать шаг за шагом и измерять производительность до и после настройки.
И да, в конечном итоге вы можете столкнуться с комбинаторным взрывом флагов компилятора, поэтому вам понадобится один или два сценария для запуска make с различными флагами компилятора, постановки заданий в очередь в большом кластере и сбора статистики времени выполнения. Если на ПК есть только вы и Visual Studio, вы потеряете интерес задолго до того, как попробуете достаточно комбинаций достаточного количества флагов компилятора.
С уважением
отметка
Когда я впервые беру фрагмент кода, я обычно могу получить в 1,4 - 2,0 раза больше производительности (т.е. новая версия кода работает в 1 / 1,4 или 1/2 времени старой версии) в пределах день или два, возясь с флагами компилятора. Конечно, это может быть комментарием к отсутствию смекалки с компиляторами у ученых, создавших большую часть кода, над которым я работаю, а не симптомом моего превосходства. Установив флаги компилятора на max (а это редко бывает просто -O3), могут потребоваться месяцы тяжелой работы, чтобы получить еще один коэффициент 1,05 или 1,1.
источник
Когда DEC выпустила свои альфа-процессоры, была рекомендация сохранить количество аргументов функции меньше 7, поскольку компилятор всегда будет пытаться автоматически помещать до 6 аргументов в регистры.
источник
Для повышения производительности сначала сосредоточьтесь на написании поддерживаемого кода - компонентного, слабосвязанного и т. Д., Поэтому, когда вам нужно изолировать часть, чтобы переписать, оптимизировать или просто профилировать, вы можете сделать это без особых усилий.
Оптимизатор незначительно повысит производительность вашей программы.
источник
Здесь вы получаете хорошие ответы, но они предполагают, что ваша программа с самого начала довольно близка к оптимальной, и вы говорите
По моему опыту, программа может быть написана правильно, но это не значит, что она близка к оптимальной. Чтобы добраться до этой точки, потребуется дополнительная работа.
Если я могу привести пример, этот ответ показывает, как совершенно разумно выглядящая программа была сделана более чем в 40 раз быстрее за счет макрооптимизации . По моему опыту, нельзя добиться большого ускорения в каждой программе в том виде, в котором она была написана изначально, но во многих (за исключением очень маленьких программ) это возможно.
После этого микрооптимизация (горячих точек) может дать вам хорошую отдачу.
источник
Я использую компилятор Intel. как в Windows, так и в Linux.
когда более или менее сделано, я профилирую код. затем зависайте в горячих точках и пытайтесь изменить код, чтобы компилятор работал лучше.
если код является вычислительным и содержит много циклов - очень полезен отчет о векторизации в компиляторе Intel - поищите в справке «vec-report».
так что основная идея - отполировать критичный по производительности код. в остальном - приоритет правильности и удобства обслуживания - короткие функции, четкий код, который можно будет понять через год.
источник
Одна оптимизация, которую я использовал в C ++, - это создание конструктора, который ничего не делает. Чтобы привести объект в рабочее состояние, необходимо вручную вызвать init ().
Это имеет преимущество в том случае, когда мне нужен большой вектор этих классов.
Я вызываю Reserve (), чтобы выделить пространство для вектора, но конструктор фактически не касается страницы памяти, на которой находится объект. Итак, я потратил некоторое адресное пространство, но на самом деле не потребил много физической памяти. Я избегаю ошибок страниц, связанных с соответствующими затратами на строительство.
Когда я создаю объекты для заполнения вектора, я устанавливаю их с помощью init (). Это ограничивает общее количество ошибок страницы и позволяет избежать необходимости изменять размер вектора при его заполнении.
источник
Одна вещь, которую я сделал, - это постаралась разместить дорогостоящие действия в тех местах, где пользователь мог бы ожидать, что программа немного задержится. Общая производительность связана с отзывчивостью, но не совсем то же самое, и для многих вещей отзывчивость является более важной частью производительности.
В последний раз, когда мне действительно приходилось улучшать общую производительность, я следил за неоптимальными алгоритмами и искал места, в которых могли возникнуть проблемы с кешем. Сначала я профилировал и измерял производительность, а затем снова после каждого изменения. Потом компания развалилась, но все равно это была интересная и поучительная работа.
источник
Я давно подозревал, но так и не доказал, что объявление массивов так, чтобы они содержали степень 2, как количество элементов, позволяет оптимизатору уменьшить силу, заменив умножение на сдвиг на количество бит при поиске отдельные элементы.
источник
val * 7
превратился в то, что иначе выглядело бы(val << 3) - val
.Поместите небольшие и / или часто вызываемые функции в начало исходного файла. Это упрощает компилятору поиск возможностей для встраивания.
источник