Я работаю над научным кодом, который очень критичен по производительности. Первоначальная версия кода была написана и протестирована, и теперь, с использованием профилировщика, пришло время запускать бритвенные циклы из горячих точек.
Хорошо известно, что некоторые оптимизации, например. циклов разворачиваются, обрабатываются в эти дни гораздо эффективнее компилятором, чем с помощью программиста, мешающего вручную. Какие методы все еще стоят? Очевидно, я запустил все, что пробовал через профилировщик, но если есть общепринятая мудрость относительно того, что работает, а что нет, это спасет меня значительное время.
Я знаю, что оптимизация очень зависима от компилятора и архитектуры. Я использую компилятор Intel С++, ориентированный на Core 2 Duo, но меня также интересует, что хорошо работает для gcc, или для "любого современного компилятора".
Вот некоторые конкретные идеи, которые я рассматриваю:
- Есть ли какая-либо польза для замены контейнеров/алгоритмов STL ручными? В частности, моя программа включает в себя очень большую очередь приоритетов (в настоящее время
std::priority_queue
), манипуляция которой занимает много времени. Является ли это чем-то стоящим в этом вопросе, или реализация STL, скорее всего, будет самой быстрой? - В то же время для
std::vector
требуемые размеры неизвестны, но имеют достаточно небольшую верхнюю границу, выгодно ли их заменять статически распределенными массивами? - Я обнаружил, что динамическое распределение памяти часто является серьезным узким местом, и устранение этого может привести к значительному ускорению. Как следствие, я заинтересован в компрометации производительности при возврате больших временных структур данных по значению по сравнению с возвратом по указателю и передачей результата по ссылке. Есть ли способ надежно определить, будет ли компилятор использовать RVO для данного метода (если, конечно, вызывающему не нужно изменять результат)?
- Насколько кэширующими являются компиляторы? Например, стоит ли искать переупорядочение вложенных циклов?
- Учитывая научный характер программы, числа с плавающей запятой используются повсюду. Существенным узким местом в моем коде были конверсии с плавающей запятой на целые числа: компилятор выдавал код, чтобы сохранить текущий режим округления, изменить его, выполнить преобразование, а затем восстановить старый режим округления --- даже если ничего в программе когда-либо меняли режим округления! Отключение этого поведения значительно ускорило мой код. Есть ли какие-либо подобные связанные с плавающей запятой ошибки, о которых я должен знать?
- Одним из следствий компиляции и связывания С++ является то, что компилятор не может выполнить то, что может показаться очень простой оптимизацией, такой как вызовы метода перемещения, такие как strlen(), из условий завершения цикла. Есть ли такая оптимизация, как эта, которую я должен искать, потому что они не могут быть выполнены компилятором и должны выполняться вручную?
- С другой стороны, есть ли какие-либо методы, которых я должен избегать, потому что они могут помешать компилятору автоматически оптимизировать код?
Наконец, чтобы пресечь некоторые виды ответов в зародыше:
- Я понимаю, что оптимизация имеет стоимость с точки зрения сложности, надежности и ремонтопригодности. Для этого конкретного приложения более высокая производительность стоит этих затрат.
- Я понимаю, что лучшая оптимизация часто помогает улучшить алгоритмы высокого уровня, и это уже сделано.