Будет ли неэффективность постфиксных операторов ++ / - оптимизирована для итераторов STL?


Я знаю, что постфиксные версии операторов инкремента / декремента обычно оптимизируются компилятором для встроенных типов (т. е. копия не будет сделана), но так ли это для iterators?

Они, по сути, просто перегруженные операторы и могут быть реализованы любым количеством способов, но поскольку их поведение строго определено, Могут ли они быть оптимизированы, и если да, то какими/многими компиляторами?

#include <vector> 

void foo(std::vector<int>& v){
  for (std::vector<int>::iterator i = v.begin();
       i!=v.end();
       i++){  //will this get optimised by the compiler?
    *i += 20;
  }
}
1 11

1 ответ:

В конкретном случае std::vector на реализации STL GNU GCC (версия 4.6.1), я не думаю, что будет разница в производительности на достаточно высоких уровнях оптимизации.

Реализация для прямых итераторов на vector обеспечивается __gnu_cxx::__normal_iterator<typename _Iterator, typename _Container>. Рассмотрим его конструктор и постфиксный оператор ++:

  explicit
  __normal_iterator(const _Iterator& __i) : _M_current(__i) { }

  __normal_iterator
  operator++(int)
  { return __normal_iterator(_M_current++); }

И его экземпляр в vector:

  typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator;

Как вы можете видеть, он внутренне выполняет постфиксное приращение для обычного указателя, а затем передает исходное значение через собственный конструктор, который сохраняет его в локальном элементе. Этот код должен быть тривиальным, чтобы устранить его с помощью анализа мертвых значений.

Но оптимизирован ли он на самом деле? Давайте выясним. Код теста:

#include <vector>

void test_prefix(std::vector<int>::iterator &it)
{
    ++it;
}

void test_postfix(std::vector<int>::iterator &it)
{
    it++;
}

Выходной узел (ВКЛ -Os):

    .file   "test.cpp"
    .text
    .globl  _Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE
    .type   _Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE, @function
_Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE:
.LFB442:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    movl    8(%ebp), %eax
    addl    $4, (%eax)
    popl    %ebp
    .cfi_def_cfa 4, 4
    .cfi_restore 5
    ret
    .cfi_endproc
.LFE442:
    .size   _Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE, .-_Z11test_prefixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE
    .globl  _Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE
    .type   _Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE, @function
_Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE:
.LFB443:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    movl    8(%ebp), %eax
    addl    $4, (%eax)
    popl    %ebp
    .cfi_def_cfa 4, 4
    .cfi_restore 5
    ret
    .cfi_endproc
.LFE443:
    .size   _Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE, .-_Z12test_postfixRN9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE
    .ident  "GCC: (Debian 4.6.0-10) 4.6.1 20110526 (prerelease)"
    .section    .note.GNU-stack,"",@progbits
Как вы можете видеть, в обоих случаях выводится одна и та же сборка. Конечно, это не обязательно относится к пользовательским итераторам или более сложным типам данных. Но оказывается, что для vector в частности, префикс и постфикс (без захвата возвращаемого значения postfix) имеют одинаковую производительность.