Для разницы производительности цикла и оптимизации компилятора

Я выбрал ответ Дэвида, потому что он был единственным, кто представил решение разницы в циклах for без флагов оптимизации. Другие ответы демонстрируют, что происходит при установке флагов оптимизации.

Ответ Джерри Коффина объяснил, что происходит при установке флагов оптимизации для этого примера. Что остается без ответа, так это то, что superCalculationA работает медленнее, чем superCalculationB, когда B выполняет одну дополнительную ссылку на память и одно дополнение для каждой итерации. Пост Немо показывает вывод ассемблера. Я подтвердил эту компиляцию с-S флаг на моем компьютере, 2.9GHz Sandy Bridge (i5-2310), работающий под управлением Ubuntu 12.04 64-bit, как предложено Matteo Italia.

Я экспериментировал с производительностью цикла for, когда наткнулся на следующий случай.

У меня есть следующий код, который выполняет одинаковые вычисления двумя разными способами.

#include <cstdint>
#include <chrono>
#include <cstdio>

using std::uint64_t;

uint64_t superCalculationA(int init, int end)
{
    uint64_t total = 0;
    for (int i = init; i < end; i++)
        total += i;
    return total;
}

uint64_t superCalculationB(int init, int todo)
{
    uint64_t total = 0;
    for (int i = init; i < init + todo; i++)
        total += i;
    return total;
}

int main()
{
    const uint64_t answer = 500000110500000000;

    std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
    double elapsed;

    std::printf("=====================================================\n");

    start = std::chrono::high_resolution_clock::now();
    uint64_t ret1 = superCalculationA(111, 1000000111);
    end = std::chrono::high_resolution_clock::now();
    elapsed = (end - start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
    std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);

    start = std::chrono::high_resolution_clock::now();
    uint64_t ret2 = superCalculationB(111, 1000000000);
    end = std::chrono::high_resolution_clock::now();
    elapsed = (end - start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
    std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);

    if (ret1 == answer)
    {
        std::printf("The first method, i.e. superCalculationA, succeeded.\n");
    }
    if (ret2 == answer)
    {
        std::printf("The second method, i.e. superCalculationB, succeeded.\n");
    }

    std::printf("=====================================================\n");

    return 0;
}

Компиляция этого кода с

g ++ main.cpp -o вывод --std = c ++ 11

приводит к следующему результату:

=====================================================
Elapsed time: 2.859 s | 2859.441 ms | 2859440.968 us
Elapsed time: 2.204 s | 2204.059 ms | 2204059.262 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================

Мой первый вопрос:почему второй цикл работает на 23% быстрее, чем первый?

С другой стороны, если я скомпилирую код с

g ++ main.cpp -o вывод --std = c ++ 11 -O1

Результаты значительно улучшаются,

=====================================================
Elapsed time: 0.318 s | 317.773 ms | 317773.142 us
Elapsed time: 0.314 s | 314.429 ms | 314429.393 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================

и разница во времени почти исчезает.

Но я не мог поверить своим глазам, когда я установил флаг -O2,

g ++ main.cpp -o вывод --std = c ++ 11 -O2

и получил это:

=====================================================
Elapsed time: 0.000 s | 0.000 ms | 0.328 us
Elapsed time: 0.000 s | 0.000 ms | 0.208 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================

Итак, мой второй вопрос:Что делает компилятор, когда я устанавливаю флаги -O1 и -O2, что приводит к этому гигантскому улучшению производительности?

Я проверилОптимизированный вариант - использование коллекции компиляторов GNU (GCC), но это не прояснило вещи.

Кстати, я компилирую этот код с g ++ (GCC) 4.9.1.

РЕДАКТИРОВАТЬ, чтобы подтвердить предположение Василия Starynkevitch

Я редактировал код, теперьmain выглядит так:

int main(int argc, char **argv)
{
    int start = atoi(argv[1]);
    int end   = atoi(argv[2]);
    int delta = end - start + 1;

    std::chrono::time_point<std::chrono::high_resolution_clock> t_start, t_end;
    double elapsed;

    std::printf("=====================================================\n");

    t_start = std::chrono::high_resolution_clock::now();
    uint64_t ret1 = superCalculationB(start, delta);
    t_end = std::chrono::high_resolution_clock::now();
    elapsed = (t_end - t_start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
    std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);

    t_start = std::chrono::high_resolution_clock::now();
    uint64_t ret2 = superCalculationA(start, end);
    t_end = std::chrono::high_resolution_clock::now();
    elapsed = (t_end - t_start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
    std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);

    std::printf("Results were %s\n", (ret1 == ret2) ? "the same!" : "different!");
    std::printf("=====================================================\n");

    return 0;
}

Эти модификации действительно увеличили время вычислений, как для-O1 а также-O2, Оба дают мне около 620 мс сейчас.Что доказывает, что -O2 действительно выполнял некоторые вычисления во время компиляции.

Я до сих пор не понимаю, что делают эти флаги для повышения производительности, и-Ofast делает еще лучше, около 320 мс.

Также обратите внимание, что я изменил порядок, в котором функции A и B вызываются для проверки предположения Джерри Коффина. Компиляция этого кода без флагов оптимизатора по-прежнему дает мне около 2,2 с в B и 2,8 с в A. Поэтому я считаю, что это не кеш. Просто подчеркиваю, что яне Говоря об оптимизации в первом случае (без флагов), я просто хочу знать, что заставляет цикл секунд работать быстрее, чем первый.

Ответы на вопрос(1)

Ваш ответ на вопрос