Являются ли container.end() и container.size () встроенными?

#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() вызываться повторно или

  1. будет ли компилятор встроить эти функции
  2. Будет ли временная переменная создана компилятором
  3. Влияют ли параметры оптимизации 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 .