it-swarm.com.de

Ist es aus Gründen der Leistung besser, std :: memcpy () oder std :: copy () zu verwenden?

Ist es besser, memcpy wie unten gezeigt zu verwenden, oder ist es aus Gründen der Leistung besser, std::copy() zu verwenden? Warum?

char *bits = NULL;
...

bits = new (std::nothrow) char[((int *) copyMe->bits)[0]];
if (bits == NULL)
{
    cout << "ERROR Not enough memory.\n";
    exit(1);
}

memcpy (bits, copyMe->bits, ((int *) copyMe->bits)[0]);
152
user576670

Ich werde hier der allgemeinen Weisheit widersprechen, dass std::copy Einen leichten, fast unmerklichen Leistungsverlust haben wird. Ich habe gerade einen Test gemacht und festgestellt, dass dies nicht der Fall ist: Ich habe einen Leistungsunterschied festgestellt. Der Gewinner war jedoch std::copy.

Ich habe eine C++ SHA-2-Implementierung geschrieben. In meinem Test habe ich 5 Strings mit allen vier SHA-2-Versionen (224, 256, 384, 512) gehasht und 300 Schleifen ausgeführt. Ich messe mal mit Boost.timer. Dieser 300er Schleifenzähler reicht aus, um meine Ergebnisse vollständig zu stabilisieren. Ich habe den Test jeweils fünfmal ausgeführt und dabei zwischen der Version memcpy und der Version std::copy Gewechselt. Mein Code nutzt die Möglichkeit, Daten in möglichst großen Datenmengen zu erfassen (viele andere Implementierungen arbeiten mit char/char *, Während ich mit T/T * (wobei T der größte Typ in der Benutzerimplementierung ist, der ein korrektes Überlaufverhalten aufweist), so dass ein schneller Speicherzugriff auf die größten Typen, die ich kann, für die Leistung meines Algorithmus von zentraler Bedeutung ist.

Zeit (in Sekunden) zum Abschließen der SHA-2-Tests

std::copy   memcpy  % increase
6.11        6.29    2.86%
6.09        6.28    3.03%
6.10        6.29    3.02%
6.08        6.27    3.03%
6.08        6.27    3.03%

Durchschnittliche Geschwindigkeitssteigerung von std :: copy over memcpy: 2,99%

Mein Compiler ist gcc 4.6.3 auf Fedora 16 x86_64. Meine Optimierungsflags sind -Ofast -march=native -funsafe-loop-optimizations.

Code für meine SHA-2-Implementierungen.

Ich habe mich dazu entschlossen, auch meine MD5-Implementierung zu testen. Die Ergebnisse waren viel weniger stabil, deshalb habe ich mich für 10 Läufe entschieden. Nach meinen ersten Versuchen hatte ich jedoch von Lauf zu Lauf sehr unterschiedliche Ergebnisse, sodass ich vermute, dass irgendeine Art von Betriebssystemaktivität im Gange war. Ich beschloss, von vorne zu beginnen.

Gleiche Compilereinstellungen und Flags. Es gibt nur eine Version von MD5 und diese ist schneller als SHA-2. Ich habe also 3000 Loops mit einem ähnlichen Satz von 5 Test-Strings durchgeführt.

Dies sind meine letzten 10 Ergebnisse:

Zeit (in Sekunden) zum Abschließen der MD5-Tests

std::copy   memcpy      % difference
5.52        5.56        +0.72%
5.56        5.55        -0.18%
5.57        5.53        -0.72%
5.57        5.52        -0.91%
5.56        5.57        +0.18%
5.56        5.57        +0.18%
5.56        5.53        -0.54%
5.53        5.57        +0.72%
5.59        5.57        -0.36%
5.57        5.56        -0.18%

Durchschnittlicher Gesamtabfall der Geschwindigkeit von std :: copy over memcpy: 0,11%

Code für meine MD5-Implementierung

Diese Ergebnisse deuten darauf hin, dass in meinen SHA-2-Tests eine Optimierung von std :: copy verwendet wurde, die std::copy In meinen MD5-Tests nicht verwenden konnte. In den SHA-2-Tests wurden beide Arrays in derselben Funktion erstellt, die std::copy/memcpy aufgerufen hat. In meinen MD5-Tests wurde eines der Arrays als Funktionsparameter an die Funktion übergeben.

Ich habe ein bisschen mehr getestet, um zu sehen, was ich tun kann, um std::copy Wieder schneller zu machen. Die Antwort stellte sich als einfach heraus: Aktivieren Sie die Optimierung der Verbindungszeit. Dies sind meine Ergebnisse mit aktiviertem LTO (Option -flto in gcc):

Zeit (in Sekunden), um die Ausführung von MD5-Tests mit -flto abzuschließen

std::copy   memcpy      % difference
5.54        5.57        +0.54%
5.50        5.53        +0.54%
5.54        5.58        +0.72%
5.50        5.57        +1.26%
5.54        5.58        +0.72%
5.54        5.57        +0.54%
5.54        5.56        +0.36%
5.54        5.58        +0.72%
5.51        5.58        +1.25%
5.54        5.57        +0.54%

Durchschnittliche Geschwindigkeitssteigerung von std :: copy over memcpy: 0,72%

Zusammenfassend scheint es keine Leistungseinbußen für die Verwendung von std::copy Zu geben. Tatsächlich scheint es einen Leistungszuwachs zu geben.

Erläuterung der Ergebnisse

Warum könnte std::copy Eine Leistungssteigerung bewirken?

Erstens würde ich nicht erwarten, dass es für eine Implementierung langsamer ist, solange die Optimierung des Inlinings aktiviert ist. Alle Compiler sind aggressiv inline; Es ist möglicherweise die wichtigste Optimierung, da es so viele andere Optimierungen ermöglicht. std::copy Kann (und ich vermute, dass dies bei allen Implementierungen in der realen Welt der Fall ist) feststellen, dass die Argumente trivial kopierbar sind und der Speicher sequentiell angeordnet ist. Dies bedeutet, dass im schlimmsten Fall, wenn memcpy legal ist, std::copy Keine schlechtere Leistung erbringen sollte. Die triviale Implementierung von std::copy, Die sich auf memcpy verschiebt, sollte die Kriterien Ihres Compilers erfüllen: "Beim Optimieren auf Geschwindigkeit oder Größe immer inline".

Mit std::copy Bleiben jedoch auch mehr Informationen erhalten. Wenn Sie std::copy Aufrufen, behält die Funktion die Typen bei. memcpy bearbeitet void *, wodurch fast alle nützlichen Informationen verworfen werden. Wenn ich beispielsweise ein Array von std::uint64_t Übergebe, kann der Compiler oder der Bibliotheksimplementierer möglicherweise die 64-Bit-Ausrichtung mit std::copy Nutzen, dies ist jedoch möglicherweise schwieriger mit memcpy. Viele Implementierungen von Algorithmen wie diesen funktionieren, indem zuerst der nicht ausgerichtete Teil am Anfang des Bereichs bearbeitet wird, dann der ausgerichtete Teil, dann der nicht ausgerichtete Teil am Ende. Wenn garantiert ist, dass alles ausgerichtet ist, wird der Code einfacher und schneller und der Verzweigungsprädiktor in Ihrem Prozessor kann leichter korrekt werden.

Vorzeitige Optimierung?

std::copy Befindet sich in einer interessanten Position. Ich erwarte, dass es niemals langsamer als memcpy und manchmal mit jedem modernen Optimierungs-Compiler schneller sein wird. Darüber hinaus können Sie alles, was Sie memcpy können, std::copy. memcpy erlaubt keine Überlappung in den Puffern, wohingegen std::copy die Überlappung in einer Richtung unterstützt (mit std::copy_backward für die andere Überlappungsrichtung). memcpy funktioniert nur mit Zeigern, std::copy funktioniert mit allen Iteratoren (std::map, std::vector, std::deque oder meinem eigenen benutzerdefinierten Typ). Mit anderen Worten, Sie sollten nur std::copy Verwenden, wenn Sie Datenblöcke kopieren müssen.

183
David Stone

Alle mir bekannten Compiler ersetzen ein einfaches std::copy mit einem memcpy, wenn es angebracht ist, oder noch besser, vektorisieren Sie die Kopie so, dass sie noch schneller ist als ein memcpy.

In jedem Fall: Profilieren und selbst herausfinden. Verschiedene Compiler erledigen unterschiedliche Aufgaben, und es ist durchaus möglich, dass sie nicht genau das tun, wonach Sie fragen.

Siehe diese Präsentation zu Compiler-Optimierungen (pdf).

Hier ist was GCC macht für ein einfaches std::copy eines POD-Typs.

#include <algorithm>

struct foo
{
  int x, y;    
};

void bar(foo* a, foo* b, size_t n)
{
  std::copy(a, a + n, b);
}

Hier ist die Demontage (mit nur -O Optimierung), zeigt den Aufruf von memmove an:

bar(foo*, foo*, unsigned long):
    salq    $3, %rdx
    sarq    $3, %rdx
    testq   %rdx, %rdx
    je  .L5
    subq    $8, %rsp
    movq    %rsi, %rax
    salq    $3, %rdx
    movq    %rdi, %rsi
    movq    %rax, %rdi
    call    memmove
    addq    $8, %rsp
.L5:
    rep
    ret

Wenn Sie die Funktionssignatur in ändern

void bar(foo* __restrict a, foo* __restrict b, size_t n)

dann wird der memmove ein memcpy für eine leichte Leistungsverbesserung. Beachten Sie, dass memcpy selbst stark vektorisiert wird.

77
Peter Alexander

Verwenden Sie immer std::copy, Da memcpy nur auf POD-Strukturen im C-Stil beschränkt ist und der Compiler wahrscheinlich Aufrufe von std::copy Durch memcpy ersetzt, wenn es sich um Ziele handelt in der Tat POD.

Außerdem kann std::copy Mit vielen Iteratortypen verwendet werden, nicht nur mit Zeigern. std::copy Ist flexibler ohne Leistungsverlust und ist der klare Gewinner.

23
Puppy

Theoretisch könnte memcpy einen geringfügigen, nicht wahrnehmbaren, infinitesimal, Leistungsvorteil haben, nur weil Es hat nicht die gleichen Anforderungen wie std::copy. Aus der Manpage von memcpy:

Um Überläufe zu vermeiden, muss die Größe der Arrays, auf die sowohl der Zielparameter als auch der Quellparameter verweisen, mindestens Anzahl Bytes betragen und darf sich nicht überlappen (zur Überlappung) Speicherblöcke, memmove ist ein sicherer Ansatz).

Mit anderen Worten, memcpy kann die Möglichkeit von Datenüberschneidungen ignorieren. (Das Übergeben überlappender Arrays an memcpy ist undefiniertes Verhalten.) Daher muss memcpy nicht explizit auf diese Bedingung prüfen, während std::copy kann verwendet werden, solange sich der Parameter OutputIterator nicht im Quellbereich befindet. Beachten Sie, dass dies nicht bedeutet, dass sich Quell- und Zielbereich nicht überschneiden dürfen.

Also seit std::copy hat etwas andere Anforderungen, theoretisch sollte es leicht (mit extremer Betonung auf leicht) langsamer sein, da es wahrscheinlich auf überlappende C- Arrays oder delegieren Sie das Kopieren von C-Arrays an memmove, das die Prüfung durchführen muss. In der Praxis werden Sie (und die meisten Profiler) wahrscheinlich nicht einmal einen Unterschied feststellen.

Natürlich, wenn Sie nicht mit PODs arbeiten, können Sie kann nicht trotzdem memcpy verwenden.

17
Charles Salvia

Meine Regel ist einfach. Wenn Sie C++ verwenden, bevorzugen Sie C++ - Bibliotheken und nicht C :)

11
UmmaGumma

Nur eine kleine Ergänzung: Der Geschwindigkeitsunterschied zwischen memcpy() und std::copy() kann sehr unterschiedlich sein, je nachdem, ob Optimierungen aktiviert oder deaktiviert sind. Mit g ++ 6.2.0 und ohne Optimierungen gewinnt memcpy() klar:

Benchmark             Time           CPU Iterations
---------------------------------------------------
bm_memcpy            17 ns         17 ns   40867738
bm_stdcopy           62 ns         62 ns   11176219
bm_stdcopy_n         72 ns         72 ns    9481749

Wenn Optimierungen aktiviert sind (-O3), Sieht alles wieder so ziemlich gleich aus:

Benchmark             Time           CPU Iterations
---------------------------------------------------
bm_memcpy             3 ns          3 ns  274527617
bm_stdcopy            3 ns          3 ns  272663990
bm_stdcopy_n          3 ns          3 ns  274732792

Je größer das Array, desto weniger fällt der Effekt auf, aber selbst bei N=1000memcpy() ist er ungefähr doppelt so schnell, wenn Optimierungen nicht aktiviert sind.

Quellcode (benötigt Google Benchmark):

#include <string.h>
#include <algorithm>
#include <vector>
#include <benchmark/benchmark.h>

constexpr int N = 10;

void bm_memcpy(benchmark::State& state)
{
  std::vector<int> a(N);
  std::vector<int> r(N);

  while (state.KeepRunning())
  {
    memcpy(r.data(), a.data(), N * sizeof(int));
  }
}

void bm_stdcopy(benchmark::State& state)
{
  std::vector<int> a(N);
  std::vector<int> r(N);

  while (state.KeepRunning())
  {
    std::copy(a.begin(), a.end(), r.begin());
  }
}

void bm_stdcopy_n(benchmark::State& state)
{
  std::vector<int> a(N);
  std::vector<int> r(N);

  while (state.KeepRunning())
  {
    std::copy_n(a.begin(), N, r.begin());
  }
}

BENCHMARK(bm_memcpy);
BENCHMARK(bm_stdcopy);
BENCHMARK(bm_stdcopy_n);

BENCHMARK_MAIN()

/* EOF */
2
Grumbel

Wenn Sie wirklich eine maximale Kopierleistung benötigen (die Sie möglicherweise nicht benötigen), verwenden Sie keine von beiden .

Es gibt ein lot, das durchgeführt werden kann, um das Kopieren des Speichers zu optimieren - noch mehr, wenn Sie bereit sind, mehrere Threads/Kerne dafür zu verwenden. Siehe zum Beispiel:

Was fehlt/suboptimal in dieser memcpy-Implementierung?

sowohl in der Frage als auch in einigen Antworten wurden Implementierungen vorgeschlagen oder Verknüpfungen zu Implementierungen hergestellt.

2
einpoklum