почему большая разница, между петлями

0

Мне было любопытно разницу между a for(;;) и for(:), особенно со скоростью между ними. Поэтому я провел небольшой тест, имея вектор из 10 миллионов целых чисел и добавляя их все вместе в for. Я обнаружил, что for(:) был 1,3 медленнее.

Что заставило бы for(:) быть намного медленнее !?

EDIT: Кажется, что for (:) использует итератор вектора, в отличие от for (;;), делающий его более длинным.

/Yu"stdafx.h "/GS/analyze-/W3/Zc: wchar_t/ZI/Gm/Od/sdl/Fd"Debug\vc120.pdb"/fp: exact/D "WIN32"/D "_DEBUG"/D "_CONSOLE"/D "_LIB"/D "_UNICODE"/D "UNICODE"/errorReport: prompt/WX-/Zc: forScope/RTC1/Gd/Oy-/MDd/Fa "Debug \"/EHsc/nologo/Fo "Debug \"/Fp"Debug\forvsForLoop.pch "

#include "stdafx.h"
#include <vector>
#include <iostream>
#include <chrono>

void init(std::vector<int> &array){
    srand(20);
    for (int x = 0; x < 10000000; x++)
        array.push_back(rand());
    return;
}

unsigned long testForLoop(std::vector<int> &array){
    unsigned long result = 0;
    for (int x = 0; x < array.size(); x++)
        result += array[x];
    return result;
}
unsigned long testFor(std::vector<int> &array){
    unsigned long result = 0;
    for (const int &element : array)
        result += element;
    return result;
}
int _tmain(int argc, _TCHAR* argv[])
{
    std::vector<int> testingArray;

    init(testingArray);

    //Warm up
    std::cout << "warming up \n";
    testForLoop(testingArray);
    testFor(testingArray);
    testForLoop(testingArray);
    testFor(testingArray);
    testForLoop(testingArray);
    testFor(testingArray);
    std::cout << "starting \n";

    auto start = std::chrono::high_resolution_clock::now();
    testForLoop(testingArray);
    auto end = std::chrono::high_resolution_clock::now();
    std::cout << "ForLoop took: " <<  std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << std::endl;


    start = std::chrono::high_resolution_clock::now();
    testFor(testingArray);
    end = std::chrono::high_resolution_clock::now();
    std::cout << "For---- took: " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << std::endl;

    system("pause");
    return 0;

}
  • 1
    В дополнение к написанию кода убедитесь, что ваши тесты выполняются с включенной оптимизацией.
  • 1
    Да, цикл for, основанный на диапазоне, использует итераторы, так же, как вы можете использовать цикл un-range-base для цикла. Смотрите, например, эту ссылку для типичной реализации. Если вы не используете итераторы в другом цикле for, то тесты не равны.
Показать ещё 4 комментария
Теги:

4 ответа

2
Лучший ответ

Чтобы убедиться, что тест не оптимизирован, я распечатал результат:

 auto x = testForLoop(......

 // ^^^
 ......nd - start).count() << "  R: " << x << std::endl;

                          //  ^^^^^^^^^^^^^^^^

Нормальный режим: (приблизительно половина скорости)

> g++ -std=c++11 v.cpp
> ./a.out
warming up
starting
ForLoop took: 33262788  R: 10739647121123056
For---- took: 51263111   R: 10739647121123056

Оптимизировано: (практически идентичное)

> g++ -O3 -std=c++11 v.cpp
> ./a.out
warming up
starting
ForLoop took: 4861314  R: 10739647121123056
For---- took: 4997957   R: 10739647121123056
3

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

Я проверил ваш код в MSVC++, GCC и Clang.

Выход GCC

ForLoop took: 7879773
For---- took: 5786831

Выход Clang

ForLoop took: 6537441
For---- took: 6743614

и MSVC++

ForLoop took: 77786200
For---- took: 249612200

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

  • 0
    Глядя на код сборки из VS2013, реализация testForLoop представляет собой хорошо развернутый код SSE, где код testFor представляет собой простой цикл сложения, поэтому testForLoop работает быстрее. Отключение SSE делает код сборки и время выполнения практически одинаковыми для обоих.
3

Если вы используете:

for ( auto x : ... )

Тогда каждый х является копией. Меньше накладных расходов может быть:

for ( const auto & x : ... )
  • 2
    Я использовал для (const int & x:)
1

Ответ - это предположение, являющееся субъективным для использования точного кода и оптимизации. Также базовая платформа может изменить поведение кода.

Существуют очень важные два метода "низкого уровня" для управления итерацией: один основан на "переназначаемом указателе", другой основан на "постоянном указателе и смещении".

В псевдокоде

loop { *a = *b; ++a; ++b; }

против

loop { a[i] = b[i]; ++i; }

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

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

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

Теперь for(a: v) {.... } является таким же, как for(auto i=v.begin(); i!=v.end(); ++i) { auto& a=*i;... } for(auto i=v.begin(); i!=v.end(); ++i) { auto& a=*i;... }

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню