#c #performance #containers #inline
#c #Производительность #контейнеры #встроенные
Вопрос:
Обычно я вступаю в дискуссию с другими и не могу подтвердить поведение — встроены ли функции container.end() и container.size(). Например, если у нас есть цикл for как таковой:
for (vector<int>::iterator it=v.begin(); it!=v.end(); it) {
//...
}
for (size_t k=0; k < v.size(); k) {
//...
}
В приведенных выше случаях будут ли функции v.end() и v.size() вызываться повторно или
- будет ли компилятор встроить эти функции
- Будет ли временная переменная создана компилятором
- Влияют ли параметры оптимизации O1 ..3 на поведение в g ?
Комментарии:
1. Ответ будет частично зависеть от конкретного контейнера, который вы используете.
2. Также, какую реализацию этого контейнера и, вполне возможно, какую версию gcc вы используете и для какой платформы.
3. Обычно, если вы получаете объект, такой как итератор, и не модифицируете его, компилятор кэширует его во временной переменной. Я также мог видеть, как он вставляет эти методы, поскольку они тривиальны (для вектора). Однако, как также упоминали другие, конкретные оптимизации зависят от компилятора, а также от того, какие настройки вы используете.
4. Я использую g в Linux с оптимизацией -03. Есть ли способ проверить, что происходит с моими настройками? Поможет ли это, если я выполню пошаговое выполнение кода с помощью gdb, если я сделаю шаг в строке цикла for? Почему для контейнеров stl с четко определенными end() и size() поведение будет другим?
5. Пошаговое выполнение кода в gdb в значительной степени гарантированно запутает вас еще больше, чем вы уже есть. Если вы должны знать ответ — имейте в виду, что есть веские основания не задавать этот вопрос — единственный способ узнать наверняка — добавить
-save-temps
в вашу команду компиляции, затем прочитать дамп сборки.
Ответ №1:
Все функции шаблона по определению являются встроенными функциями. Компилятор может сделать их вызываемыми функциями, особенно если это компиляция в режиме отладки, но наиболее вероятным результатом является то, что код будет встроен.
Возможно, но маловероятно, что временная переменная будет создана автоматически. Как компилятор определяет, повлияет ли код в цикле на возвращаемые значения v.end() или v.size()? Я подозреваю, что большинство не беспокоятся, хотя у меня нет никаких доказательств в любом случае.
Комментарии:
1. Функции шаблона не обязательно должны быть встроенными и, конечно же, не обязаны быть встроенными по определению. Это просто помогает созданию экземпляра (что, очевидно, важно). Они могут быть определены отдельно в их объявлении и потенциально должны быть явно созданы.
2. Компилятор определенно будет беспокоиться. Вопрос в том, может ли он сказать наверняка, что возвращаемое значение size () / end () не может измениться. Если он может «видеть» код всех функций, вызываемых в цикле, он часто может. В таких случаях многие компиляторы будут извлекать / вычислять результат size() / end () только один раз и повторно использовать его для каждой итерации цикла.
3. GCC считает, что функции-члены шаблона, определенные вне тела класса, не являются встроенными. Это можно продемонстрировать с помощью
-fvisibility-inlines-hidden
флага. Встроенные определения (шаблонные или нет) будут рассматриваться как скрытые. Нестандартные определения шаблонов будут рассматриваться как скрытые, только если они явно помечены какinline
.4. Шаблоны функций подчиняются тем же правилам для встраивания, что и шаблоны, не являющиеся шаблонными. Функция, определенная внутри определения класса, считается встроенной, функция, объявленная с
inline
ключевым словом, считается встроенной, а другие определения функций — нет. Итак, если вы определяете шаблон функции вне класса, без использованияinline
ключевого слова, шаблон функции не будет встроенным.
Ответ №2:
Даже если функции встроенные, вы не можете рассчитывать на то, что компилятор правильно оптимизирует цикл, если вложенный код достаточно большой / сложный. Это потому, что семантически вы, вероятно, не ожидаете, что end()
значение будет меняться на каждой итерации цикла, поэтому его следует вычислять только один раз и. Однако компилятор может быть не в состоянии обеспечить эту гарантию, основываясь на соображениях псевдонимирования и других условиях «отказа» в оптимизаторе. Если -как отвечали другие постеры — вы предварительно вычисляете end()
и сохраняете это в переменной, у компилятора меньше шансов запутаться.
Например:
typedef std::vector<int> intvec;
intvec v = external_function();
for (intvec::const_iterator vi = v.begin(); vi != v.end(); vi) {
call_external_function(v, *vi);
}
С точки зрения компилятора, call_external_function()
может измениться размер вектора. Если вы знаете, что этого не может произойти, вы должны сообщить об этом компилятору:
for (intvec::const_iterator vi = v.begin(), ve = v.end(); vi != ve; vi) {
call_external_function(v, *vi);
}
Ответ №3:
Даже при том, что функции шаблона встроены, трудно сказать, что компилятор обеспечит ту же степень оптимизации, которая нам нужна. Можно использовать следующие методы.
Хранить v.end()
во временном vEnd
:
for(vector<TYPE>::iterator it = v.begin(), vEnd = v.end(); it != vEnd; it ) {}
Выполните цикл в обратном порядке:
for (size_t k = v.size() - 1; k != (size_t)(-1) ; --k) { }
Комментарии:
1. обратный
for
цикл переменная цикла без знака>= 0
= катастрофа2. @pgroke, почему это катастрофа? Мне интересно знать.
3. потому что,
k
будучи без знака,k >= 0
будет всегда быть правдой. таким образом, цикл будет выполняться вечно. когдаk
значение достигнет нуля, оно снова уменьшится, что вызовет недостаточный поток. при переполнении будет получено наибольшее число, котороеk
может вместиться (т.Е.,0xffffffff
еслиk
оно имеет ширину 32 бита).4. Это все еще неверно, поскольку
k
по-прежнему беззнаково и все равно будет переполняться. После выполнения underflow преобразование его вlong int
либо даст вам положительное число (еслиlong int
оно достаточно широкое, чтобы вместить макс. значениеsize_t
) или неопределенное поведение (еслиlong int
оно недостаточно широкое). Лучшее (простое) решение, которое я знаю, — это изменить типk
наptrdiff_t
(который подписан и должен быть достаточно широким, чтобы содержать любое значение, котороеvector::size()
может когда-либо вернуться). т.е.for (ptrdiff_t k = v.size() - 1; k >= 0 ; --k) { }
5. @pgroke: Или использовать
k != (size_t)-1
.