В моем проекте у меня есть класс, где время выполнения — первая цель. Из-за этого меня не волнует обслуживание, заказ и так далее. По крайней мере, мне было наплевать до вчерашнего дня … Теперь я нахожусь в ситуации, когда я тоже должен немного волноваться об этом.
У меня есть класс, скажем, A, который выполняет многократное сканирование изображений, поступающих с камеры, то есть окно с переменной шириной сканирует их в режиме реального времени.
class A{
// methods and attributes of A:
...
void runiterator(){
...
for{ // change window’s dimension
for{ // rows
for{ // columns
// many lines of code of operations to be executed for each window at each position
...
}
}
}
}
};
Производительность уже показывает небольшую задержку, но я мог бы ее решить, пропустив ограниченную область изображения. Кроме того, у меня есть вторая функция, скажем, B, которая имеет точно такую же схему, что и A, и выполняет различные операции при каждом сканировании (и, к счастью, намного быстрее, чем A).
Что ж, теперь пришло время объединить все операции, чтобы значительно улучшить общий результат. Только то, что код станет действительно запутанным, огромным и смешивающим вещи, которые действительно отличаются. Я подумал определить класс X, который выполняет итерации и при каждом сканировании выполняет вызовы функций для одной функции в A_new и одной в B_new. Но я обеспокоен тем, что около 200000×2 вызовов функций на изображение может привести к снижению производительности.
Какой твой совет?
РЕДАКТИРОВАТЬ
С классом X, который вызывает только Anew (так что его можно сравнить только с тем, что сейчас есть A), я получаю в среднем много повторений:
Время выполнения X на серии из 56 изображений = 6,15 с
Время выполнения A на одной серии из 56 изображений = 5,98 с
Кажется, мои подозреваемые были не так наивны.
Разница составляет около 3%, не так много, но все же извините за потерю.
С __forceinline время для X равно 5,98 с, но я бы предпочел не полагаться на него.
Я думаю, что код оптимизирован и поля для дальнейших улучшений очень малы.
На самом деле он делает много вещей на изображениях за относительно короткое время.
Последовательная обработка данных в классе А невозможна, поскольку она основана на значениях, полученных на изображениях, которые непредсказуемы. Это причина, по которой класс B (которому это удается) намного быстрее.
Вы действительно должны измерить, что это вызывает проблемы с производительностью, прежде чем беспокоиться об этом.
Если там является проблема, попробуйте сделать это с помощью шаблонов. Напишите два варианта функций, затем используйте их как функторы в шаблоне функции, который выполняет итерацию. Вы создадите обе версии и вызовете соответствующую. Компилятор должен встроить вызовы (но лучше проверить это).
Я использовал это для манипулирования медицинскими изображениями, и это сработало как шарм.
Затраты на вызов функции во многом зависят от типа функции, которую вы вызываете. На уровне ассемблера (при условии отсутствия хитрой обработки ошибок страницы ОС) call address
Инструкция на современном процессоре Intel занимает 0 циклов (то же самое верно для jmp address
инструкция). Накладные расходы вводятся, если адрес функции вычисляется из какого-либо источника данных, например, поиска в vtbl, вызова внешней DLL (если вы используете Win32) или при условии наличия условия. Они включают доступ к памяти и загрязнение кеша. Что подводит нас к бигги.
Большая часть производительности теряется при ожидании поступления данных в ЦП. Скорость ЦП намного выше скорости чтения данных из ОЗУ. Вот почему существует несколько уровней кэширования, каждый уровень обычно больше и медленнее, чем предыдущий уровень. Стоимость вызова функции, даже сложной, меньше, чем потеря времени на потерю кэша при чтении данных.
Такие вещи подпадают под заголовок: микрооптимизация.
Как правило, избегайте случайного доступа к данным, обрабатывайте данные последовательно, т.е. выполняйте элемент n, затем n + 1, n + 2 и т. Д., А не n, n + 100, n + 200 и т. Д., N + 1, n + 101, n +201 и др.
Кроме того, дайте компилятору возможность встроить функции — таким образом будет выполнено встраивание, если результат даст более быстрый код (и у компилятора есть очень хорошее представление о том, когда это выгодно).
Также обратите внимание, что большие функции могут быть медленнее, чем множество мелких функций (это связано с буфером мопов, который кэши ЦП локально). Итерации по данным несколько раз могут быть быстрее, чем делать все за один раз. Только профилирование кода скажет вам, какой из них быстрее.
Наконец, лучший алгоритм — это обычно путь к повышению производительности. Ваш алгоритм оптимален?
Вы должны измерить эффекты, так как трудно сказать, что на самом деле производит компилятор — особенно в O3
, Конечно, вызовы функций имеют издержки, если функции не встроены компилятором. Попробуйте предоставить inline
подсказка компилятору, если функция может быть встроенной.