Итераторы C++ и оптимизация цикла

Глядя на ваш пример, это должно выглядеть примерно так:

it('Check click function on Transactionhash', () => {
  let name = ''
  cy.get('td').eq(1)
    .then(incharge => {
      name = incharge.text()
      cy.get('td')
        .eq(1)
        .click()
      cy.url()
        .should('eq',`https://worldmap.com/city/${name}`)
  })
})

Обратите внимание на использование обратных кавычек (`) в функции should ()

46
задан Jon Seigel 4 April 2010 в 08:48
поделиться

11 ответов

Я просто упомяну для записи, что стандарт C ++ предписывает, что вызовы begin () и end () для any тип контейнера (будь то вектор , список , карта и т. Д.) должен занимать только постоянное время . На практике эти вызовы почти наверняка будут встроены в сравнение с одним указателем, если вы компилируете с включенными оптимизациями.

Обратите внимание, что эта гарантия не обязательно распространяется на дополнительные поставляемые поставщиком «контейнеры», которые на самом деле не подчиняются формальным требованиям. быть контейнером, изложенным в главе 23 стандарта (например, односвязный список slist ).

29
ответ дан 26 November 2019 в 20:13
поделиться

Хотя эти две версии не одинаковы. Во второй версии он сравнивает итератор с list.end () каждый раз, и то, что вычисляет list.end () , может измениться в течение цикла. Теперь, конечно, вы не можете изменить список через const_iterator it ; но ничто не мешает коду внутри цикла просто вызывать методы из списка списка и изменять его, что может (в зависимости от того, какой тип структуры списка ) изменить конечный итератор. Поэтому в некоторых обстоятельствах может быть неправильным сохранять конечный итератор заранее, потому что он может перестать быть правильным конечным итератором к тому времени, когда вы доберетесь до него.

43
ответ дан 26 November 2019 в 20:13
поделиться

The first one will probably almost always be faster, but if you think this will make a difference, always profile first to see which is faster and by how much.

The compiler will probably be able to inline the call to end() in both cases, although if end() is sufficiently complicated, it may opt not to inline it. However, the key optimization is whether or not the compiler can perform loop-invariant code motion. I would posit that in most cases, the compiler can't be certain that the value of end() won't change during the iteration of the loop, in which case it has no choice but to call end() after each iteration.

11
ответ дан 26 November 2019 в 20:13
поделиться

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

ссылаясь на ваш пример, первая версия делает копию итератора end () , вызывая любой код, выполняемый для конструктора копирования объекта итератора. Контейнеры STL обычно содержат встроенные функции end () , поэтому у компилятора есть много возможностей оптимизировать вторую версию, даже если вы не пытаетесь помочь ей. Какой из них лучше? Измерьте их.

8
ответ дан 26 November 2019 в 20:13
поделиться

Вы можете сделать первую версию более краткой и получить лучшее из обоих:

for( const_iterator it = list.begin(), ite = list.end();
     it != ite; ++it)

PS Итераторы не являются константными, они являются итераторами константной ссылки. Есть большая разница.

6
ответ дан 26 November 2019 в 20:13
поделиться

Рассмотрим этот пример:

for (const_iterator it = list.begin(); it != list.end(); ++list)
{
    if (moonFull())
        it = insert_stuff(list);
    else
        it = erase_stuff(list);
}

в этом случае вам НУЖНО вызвать list.end () в цикле, и компилятор не собирается его оптимизировать.

Другое В случаях, когда компилятор может доказать, что end () всегда возвращает одно и то же значение, оптимизация может иметь место.

Если мы говорим о контейнерах STL, то я думаю, что любой хороший компилятор может оптимизировать несколько вызовов end (), когда несколько end () вызовы не нужны для логики программирования. Однако, если у вас есть пользовательский контейнер и реализация end () не находится в одном и том же модуле перевода, тогда оптимизация должна произойти во время соединения. Я очень мало знаю об оптимизации времени ссылок, но держу пари, что большинство линкеров не будут делать такую ​​оптимизацию.

6
ответ дан 26 November 2019 в 20:13
поделиться

Ага, люди, кажется, угадывают. Откройте свой код в отладчике, и вы увидите, что при вызовах begin (), end () и т. Д. Все оптимизировано. Нет необходимости использовать версию 1. Протестировано с помощью компилятора Visual C ++.

4
ответ дан 26 November 2019 в 20:13
поделиться

The compiler might be able to optimize the second into the first, but that assumes that the two are equivalent, i.e. end() actually is constant. A slightly more problematic issue is that the compiler may be unable to deduce that the end iterator is constant due to possible aliasing. However, assuming that the call to end() is inlined, the difference is just a memory load.

Note that this assumes that the optimizer is enabled. If the optimizer is not enabled, as is often done in debug builds, then the second formulation will involve N-1 more function calls. In current versions of Visual C++, debug builds will also incur additional hits due to function prolog/epilog checks and heavier debug iterators. Therefore, in STL heavy code, defaulting to the first case can prevent code from being disproportionately slow in debug builds.

Insertion and removal within the loop are a possibility, as others have pointed out, but with this style of loop I find that unlikely. For one thing, node-based containers -- list, set, map -- don't invalidate end() on either operation. Second, the iterator increment frequently has to be moved in-loop to avoid invalidation problems:

   // assuming list -- cannot cache end() for vector
   iterator it(c.begin()), end(c.end());
   while(it != end) {
       if (should_remove(*it))
           it = c.erase(it);
       else
           ++it;
   }

Thus, I consider a loop that claims to call end() for mutate-during-loop reasons and still has ++it in the loop header to be suspect.

4
ответ дан 26 November 2019 в 20:13
поделиться
  1. Попробуйте его в стрессовых условиях и посмотрите, часто ли вы используете ** этот код ***.
    Если нет, это не имеет значения.

  2. Если вы посмотрите на дизассемблирование или пошагово.
    Вот как вы можете определить, что быстрее.

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

** (Где «в» означает на самом деле в нем или вызывается из него.)

*** (Где «часто» означает значительный процент времени .)

ДОБАВЛЕНО: не просто посмотрите, сколько раз в секунду выполняется код. Это может быть 1000 раз в секунду и все еще использовать менее 1% времени.

Не нужно также время, сколько времени это займет. Это может занять миллисекунду и все еще использовать менее 1% времени.

2
ответ дан 26 November 2019 в 20:13
поделиться

I always preferred the first one. Though with inline functions, compiler optimizations and relatively smaller container size ( in my case it's normally max 20-25 items) it really does not make any large difference with respect to performace.

const_iterator it = list.begin();
const_iterator endIt = list.end();

for(; it != endIt ; ++it)
{//do something
}

But recently I am using more of std::for_each wherever it's possible. Its optimized looping which helps to make the code look more readable than other two.

std::for_each(list.begin(), list.end(), Functor());

I will use the loop only when std::for_each cannot be used. (for ex: std::for_each does not allow you to break the loop unless an exception is thrown).

1
ответ дан 26 November 2019 в 20:13
поделиться

Теоретически, компилятор может оптимизировать вторую версию в первую (очевидно, если контейнер не изменяется во время цикла).

На практике я обнаружил несколько подобных случаев при профилировании критичного по времени кода, где Мой компилятор полностью не смог вывести инвариантные вычисления из условий цикла. Поэтому, хотя в большинстве случаев и более краткая версия подойдет, я не полагаюсь на то, что компилятор будет делать с ней какие-то разумные действия для случая, когда я действительно обеспокоен производительностью.

0
ответ дан 26 November 2019 в 20:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: