Я все еще подозреваю, что заданный вопрос - это не тот вопрос, который был задуман, но мне пришло в голову, что суть моего ответа, скорее всего, не изменится.Если вопрос обновляется, я всегда могу отредактировать этот ответ, чтобы он соответствовал (или удалял его, если он оказался неприменимым).
Отмена приоритетов оптимизации
Существуют различные факторы, которые должнывлияет на то, как вы пишете свой код.Среди желаемых целей - оптимизация пространства, оптимизация времени, инкапсуляция данных, логическая инкапсуляция, удобочитаемость, надежность и правильная функциональность.В идеале все эти цели были бы достижимы в каждом фрагменте кода, но это не особенно реалистично.Гораздо более вероятна ситуация, когда одна или несколько из этих целей должны быть принесены в жертву в пользу других. Когда это происходит, оптимизации, как правило, должны уступать всему остальному.
Это не означает, что оптимизации следует игнорировать.Существует множество оптимизаций, которые редко мешают достижению более приоритетных целей.Они варьируются от малых, таких как передача по константной ссылке, а не по значению, до больших, таких как выбор логарифмического алгоритма вместо экспоненциального.Однако оптимизацию, которая мешает достижению других целей, следует отложить до тех пор, пока ваш код не будет достаточно завершен и не будет работать правильно.В этот момент следует использовать профилировщик, чтобы определить, где на самом деле находятся узкие места.Эти узкие места являются единственными местами, где другие цели должны поддаваться оптимизации, и только если профилировщик подтверждает, что оптимизации достигли своих целей.
Для задаваемого вопроса это означает, что основнаяпроблема не в вычислительных затратах, а в инкапсуляции.Почему вызывающий func()
должен выделять место для func()
для работы?Это не должно происходить, если только профилировщик не определит это как узкое место в производительности.И если бы это сделал профилировщик, было бы гораздо проще (и надежнее!) Спросить профилировщика, помогает ли изменение, чем задавать переполнение стека.
Почему?
Я могу подуматьдве основные причины для расстановки приоритетов оптимизации.Во-первых, «тест на нюх» ненадежен.Хотя может быть несколько человек, которые могут определить узкие места, глядя на код, многие, многие просто думают, что могут.Во-вторых, поэтому у нас есть оптимизирующие компиляторы.Это не неслыханно для кого-то, чтобы придумать этот сверхумный трюк оптимизации только, чтобы обнаружить, что компилятор уже делал это.Держите ваш код чистым, и пусть компилятор обрабатывает рутинные оптимизации.Только шаг, когда задача явно превышает возможности компилятора.
См. Также: преждевременная оптимизация
Выбор оптимизации
Хорошо, предположим, что профилировщик идентифицировал конструкцию этого небольшого массива из 10 элементов как узкое место.Следующий шаг - проверить альтернативу, верно?Почти.Во-первых, вам нужна альтернатива, и я считаю, что обзор теоретических преимуществ различных альтернатив будет полезен.Просто имейте в виду, что это теоретически, и что профайлер получает последнее слово.Поэтому я расскажу о плюсах и минусах альтернатив из этого вопроса, а также о некоторых других альтернативах, которые могут быть рассмотрены.Давайте начнем с худших вариантов, пробираясь к лучшим.
Пример A
В Примере A вектор создается с 5 элементами, затем элементы помещаются в вектор до тех пор, пока i
соответствует или превышает размер вектора.Видя, как i
и размер вектора увеличиваются на одну каждую итерацию (и i
начинается меньше размера), этот цикл будет работать до тех пор, пока вектор не станет достаточно большим, чтобы вызвать сбой программы.Это означает, вероятно, миллиарды итераций (несмотря на утверждение вопроса о том, что размер не будет превышать 10).
Легко самый вычислительно дорогой вариант.Не делай этого.
Пример B
В примере B вектор создается для каждой итерации внешнего цикла while
, который затем доступен по ссылке из func()
.Минусы производительности здесь включают передачу параметра в func()
и наличие func()
косвенного доступа к вектору через ссылку.Нет никаких плюсов производительности, так как это делает все, что базовая линия (см. Ниже) сделает плюс некоторые дополнительные шаги.
Даже если компилятор может компенсировать минусы, я вижунет смысла пробовать этот подход.
Baseline
Базовая линия, которую я использую, является исправлением бесконечного цикла примера A.В частности, замените "my_vec.push_back(i);
" примером "B" my_vec[i] = i;
.Этот простой подход соответствует тому, что я ожидал от первоначальной оценки профилировщика.Если вы не можете победить простое, придерживайтесь его.
Пример B *
Текст вопроса представляет неточную оценку Примера B. Интересно, что оценка описывает подход, который потенциально можетулучшить на исходном уровне.Чтобы получить код, соответствующий текстовому описанию, переместите «std::vector<double> my_vec(5, 0.0);
» в примере B в строку непосредственно перед оператором while
.Это дает эффект построения вектора только один раз, а не построения его с каждой итерацией.
Минусы этого подхода те же, что и в примере B, который изначально был закодирован.Тем не менее, теперь мы получаем усиление в том, что конструктор вектора вызывается только один раз.Если построение обходится дороже, чем затраты на косвенное обращение, результатом должно стать чистое улучшение, если цикл while
повторяется достаточно часто.(Остерегайтесь этих условий: это значительное «если», и нет никакого априорного предположения о том, сколько итераций «достаточно».) Было бы разумно попробовать это и посмотреть, что говорит профилировщик.
Getнекоторая статическая
Вариант в Примере B *, который помогает сохранить инкапсуляцию, заключается в использовании базовой линии (фиксированный Пример A), но перед объявлением вектора ключевым словом static
.Это дает преимущество построения вектора только один раз, но без дополнительных затрат, связанных с созданием вектора в качестве параметра.Фактически, преимущество может быть больше, чем в примере B *, поскольку построение происходит только один раз за выполнение программы, а не каждый раз при запуске цикла while
.Чем больше раз запускается цикл while
, тем больше это преимущество.
Основным условием здесь является то, что вектор будет занимать память во время выполнения программы.В отличие от примера B *, он не освободит свою память, когда закончится блок, содержащий цикл while
.Использование этого подхода в слишком многих местах приведет к раздуванию памяти.Таким образом, хотя это целесообразно для профилирования этого подхода, вы можете рассмотреть другие варианты.(Конечно, если профилировщик называет это как узкое место, затмевая все остальные, стоимость достаточно мала, чтобы заплатить.)
Исправьте размер
Мой личный выбордля какой оптимизации здесь попробовать начать с базовой линии и переключить вектор на std::array<10,double>
.Моя главная мотивация заключается в том, что необходимый размер не будет больше 10. Также важно, чтобы конструкция double
была тривиальной.Построение массива должно быть на одном уровне с объявлением 10 переменных типа double
, что, как я ожидаю, будет незначительным.Так что нет необходимости в хитроумных трюках по оптимизации.Просто дайте компилятору сделать свое дело.
Ожидаемое возможное преимущество этого подхода состоит в том, что vector
выделяет пространство в куче для его хранилища, что имеет накладные расходы.Местный array
не будет иметь эту стоимость.Однако это только возможная выгода.Векторная реализация могла бы уже использовать это соображение производительности для малых векторов.(Возможно, он не использует кучу, пока емкость не должна превысить какое-то магическое число, возможно, больше 10.) Я бы сослался на вас ранее, когда упомянул «супер-умный» и «компилятор уже делал это».
Я бы запустил это через профилировщик. Если нет никакой пользы, скорее всего, нет никакой пользы от других подходов. Дайте им попытку, конечно, поскольку они достаточно просты, но, вероятно, будет лучше использовать ваше время, чтобы взглянуть на другие аспекты для оптимизации.