Für Schleifenleistungsunterschiede und Compileroptimierung

Ich habe mich für Davids Antwort entschieden, weil er der einzige war, der eine Lösung für den Unterschied in den for-Schleifen ohne Optimierungsflags vorlegte. Die anderen Antworten zeigen, was passiert, wenn die Optimierungsflags gesetzt werden.

n der Antwort von @Jerry Coffin wurde erklärt, was passiert, wenn die Optimierungsflags für dieses Beispiel gesetzt werden. Unbeantwortet bleibt, warum superCalculationA langsamer als superCalculationB ausgeführt wird, wenn B für jede Iteration eine zusätzliche Speicherreferenz und eine Addition durchführt. Nemos Beitrag zeigt die Assembler-Ausgabe. Ich habe das Kompilieren mit dem @ bestäti-S flag auf meinem PC, 2,9 GHz Sandy Bridge (i5-2310), mit Ubuntu 12.04 64-Bit, wie von Matteo Italia vorgeschlagen.

Ich habe mit der Leistung von for-loops experimentiert, als ich auf den folgenden Fall gestoßen bin.

Ich habe den folgenden Code, der dieselbe Berechnung auf zwei verschiedene Arten ausführt.

#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;
}

Kompilieren Sie diesen Code mit

g ++ main.cpp -o Ausgabe --std = c ++ 11

führt zu folgendem Ergebnis:

=====================================================
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.
=====================================================

Meine erste Frage ist:Warum läuft die zweite Schleife 23% schneller als die erste?

Andererseits, wenn ich den Code mit @ kompilie

g ++ main.cpp -o Ausgabe --std = c ++ 11 -O1

Die Ergebnisse verbessern sich sehr,

=====================================================
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.
=====================================================

und der Zeitunterschied verschwindet fast.

Aber ich konnte meinen Augen nicht trauen, als ich die -O2 Flagge gesetzt habe,

g ++ main.cpp -o Ausgabe --std = c ++ 11 -O2

und habe folgendes:

=====================================================
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.
=====================================================

Also, meine zweite Frage ist:Was macht der Compiler, wenn ich -O1- und -O2-Flags setze, die zu dieser gigantischen Leistungsverbesserung führen?

Ich überprüfteOptimized Option - Verwenden der GNU Compiler Collection (GCC), aber das hat die Dinge nicht geklärt.

Übrigens kompiliere ich diesen Code mit g ++ (GCC) 4.9.1.

EDIT bestätigt die Annahme von Basile Starynkevitch

Ich habe den Code bearbeitet, jetztmain sieht aus wie das

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;
}

Diese Änderungen haben die Rechenzeit erheblich verlängert, sowohl für-O1 und-O2. Beide geben mir jetzt ungefähr 620 ms.Was beweist, dass -O2 zur Kompilierungszeit wirklich einige Berechnungen durchgeführt hat.

Ich verstehe immer noch nicht, was diese Flags tun, um die Leistung zu verbessern, und-Ofast macht es noch besser, bei etwa 320ms.

Beachten Sie auch, dass ich die Reihenfolge geändert habe, in der die Funktionen A und B aufgerufen werden, um die Annahme von Jerry Coffin zu testen. Wenn ich diesen Code ohne Optimierer-Flags kompiliere, habe ich immer noch ungefähr 2,2 Sekunden in B und 2,8 Sekunden in A. Also denke ich, dass es keine Cache-Sache ist. Ich verstärke nur, dass ich @ bnich Sprechen über Optimierung im ersten Fall (die ohne Flags), ich möchte nur wissen, was macht die Sekunden-Schleife schneller als die erste.