Frage

Wir haben Core2 Maschinen (Dell T5400) mit XP64.

Wir beobachten, dass, wenn 32-Bit-Prozesse, die Leistung von memcpy ist in der Größenordnung von 1.2GByte / s; jedoch MEMCPY in einem 64-Bit-Prozess erreicht über 2.2GByte / s (oder 2.4GByte / s mit dem Intel-Compiler CRTs Memcpy). Während erste Reaktion könnte sein, nur das erklären weg, als aufgrund des breiteren Register verfügbar in 64-Bit-Code, beobachten wir, dass unsere eigenen Memcpy artigen SSE Assembler-Code (die 128-Bit verwenden sollten breite Last speichert unabhängig von 32/64-Bitwert von Das Verfahren) veranschaulicht, ähnlich Obergrenzen für die Kopie Bandbreite erreicht es.

Meine Frage ist, was dieser Unterschied ist eigentlich wegen? Do 32-Bit-Prozesse zu springen haben durch einige zusätzliche WOW64 Reifen im RAM zu bekommen? Ist es etwas, zu tun mit TLBs oder Prefetchers oder ... was?

Vielen Dank für jede Einsicht.

Auch auf Intel-Foren .

War es hilfreich?

Lösung

Natürlich müssen Sie wirklich an den tatsächlichen Maschinenbefehlen suchen, die innerhalb der innersten Schleife des Memcpy ausgeführt werden, indem sie mit einem Debugger in den Maschinencode zu treten. Alles andere ist nur Spekulation.

Meine quess ist, dass es wahrscheinlich nichts hat, mit 32-Bit zu tun im Vergleich zu 64-Bit per se; Meine Vermutung ist, dass die schnellere Bibliotheksroutine geschrieben wurde SSE nicht-zeitliche Geschäfte verwendet wird.

Wenn die innere Schleife enthält jede Variation der herkömmlichen Lade-Speichere-Befehle, dann muss der Zielspeicher in die Maschine Cache, verändert und zurückgeschrieben ausgelesen werden. Da das Lese völlig unnötig ist - die Bits gelesen werden sofort überschrieben werden - Sie durch die Verwendung der „nicht-temporal“ Schreibbefehle die Hälfte der Speicherbandbreite sparen, die die Caches umgehen. Auf diese Weise wird der Zielspeicher nur geschrieben machte eine Einweg Reise in den Speicher statt einer Rundreise.

Ich weiß nicht, die CRT-Bibliothek von Intel Compiler, so ist dies nur eine Vermutung. Es gibt keinen besonderen Grund, warum die 32-Bit-libCRT nicht das gleiche tun können, aber die Beschleunigung Sie zitieren ist im Baseballstadion von dem, was ich durch Umwandlung der MOVDQA Anweisungen nur erwarten würde movnt ...

Da Memcpy wird keine Berechnungen zu tun, es ist immer gebunden, wie schnell können Sie lesen und Speicher schreiben.

Andere Tipps

Ich denke, folgende erklären kann:

Um die Daten aus dem Speicher in ein Register zu kopieren und zurück in den Speicher, was Sie tun

mov eax, [address]
mov [address2], eax

Dies bewegt 32 Bit (4 Byte) von Adresse Adresse 2. Das gleiche gilt mit 64 Bit in 64-Bit-Modus

mov rax, [address]
mov [address2], rax

Dies bewegt 64 Bit, 2 Byte, von Adresse zu address2. „Mov“ selbst, unabhängig davon, ob es sich um 64-Bit oder 32-Bit eine Latenzzeit von 0,5 und einen Durchsatz von 0,5 nach Intel-Spezifikationen hat. Die Latenz ist, wie viele Taktzyklen der Befehl durch die Pipeline zu erreichen und zum Durchsatz ist, wie lange die CPU warten muss, bevor er wieder den gleichen Befehl zu akzeptieren. Wie Sie sehen können, kann es zwei mov der pro Taktzyklus tun, ist es jedoch einen halben Taktzyklus zwischen zwei MOV warten muss, so kann es effektiv tun nur eine mov pro Taktzyklus (oder bin ich hier falsch und die Begriffe falsch interpretieren? siehe PDF hier für Details).

Natürlich ist ein mov reg, mem kann mehr als 0,5 Zyklen sein, je nachdem, ob die Daten in der 1. oder 2. Level-Cache ist, oder nicht im Cache überhaupt und muss aus dem Speicher gepackt werden. Ignoriert jedoch die Latenzzeit von über diese Tatsache (als PDF-Staaten I oben verlinkten), nimmt er alle notwendigen Daten für die mov vorhanden sind bereits (sonst wird die Latenz von erhöhen, wie lange es dauert, um die Daten von holen, wo es ist jetzt - dies mehrere Taktzyklen sein könnte und ist völlig unabhängig von dem Befehl sagt auf Seite 482 / C-30)

das PDF ausgeführt wird.

Was ist interessant, ob die mov 32 oder 64 Bit spielt keine Rolle. Das bedeutet, es sei denn, die Speicherbandbreite der begrenzende Faktor wird, 64-Bit-MOVs sind gleich schnell auf 32 bit MOV, und da es nur halb so viele MOVs nimmt die gleiche Datenmenge von A zu bewegen, B, wenn 64-Bit verwendet wird, der Durchsatz (in der Theorie) doppelt so hoch sein (die Tatsache, dass es nicht wahrscheinlich, weil der Speicher nicht unbegrenzt schnell ist).

Okay, jetzt denken Sie, wenn die größeren SSE-Register verwenden, sollten Sie einen schnelleren Durchsatz, nicht wahr? AFAIK die XMM-Register sind nicht 256, sondern 128 Bit breit, BTW ( Verweis auf Wikipedia ). Sie haben jedoch Latenz und Durchsatz in Betracht gezogen? Entweder die Daten, die Sie verschieben möchten, ist 128 Bit ausgerichtet ist oder nicht. Abhängig davon, bewegen Sie sie entweder mit

movdqa xmm1, [address]
movdqa [address2], xmm1

oder, wenn nicht ausgerichtet

movdqu xmm1, [address]
movdqu [address2], xmm1

Nun, MOVDQA / movdqu hat eine Latenzzeit von 1 und einem Durchsatz von 1. So die Anweisungen nehmen doppelt so lang ausgeführt werden und die Wartezeit nach den Anweisungen ist doppelt so lang wie ein normaler mov.

Und noch etwas haben wir noch nicht einmal berücksichtigt, ist die Tatsache, dass die CPU tatsächlich teilt Befehle in Mikro-ops und es kann diese parallel ausführen. Jetzt beginnt es wirklich kompliziert zu werden ... noch zu kompliziert für mich.

Wie auch immer, ich weiß aus Erfahrung, Laden von Daten zu / von XMM-Registern ist viel langsamer als Laden von Daten in / aus dem normalen Register, so dass Ihre Idee Übertragung zu beschleunigen von XMM Registern wurde von der ersten Sekunde an zum Scheitern verurteilt. Ich bin eigentlich überrascht, dass die SSE memmove nicht viel langsamer als die normalen am Ende ist.

Ich habe endlich auf den Grund dieser (und Antwort in Sente des sterben war auf dem richtigen Weg, danke)

In der nachstehenden dst und src sind 512 MByte std :: vector. Ich bin mit dem Intel-Compiler 10.1.029 und CRT.

Auf 64-Bit beide

  

memcpy(&dst[0],&src[0],dst.size())

und

  

memcpy(&dst[0],&src[0],N)

wobei N vorher const size_t N=512*(1<<20); deklariert rufen

  

__intel_fast_memcpy

der Großteil davon besteht aus:

  000000014004ED80  lea         rcx,[rcx+40h] 
  000000014004ED84  lea         rdx,[rdx+40h] 
  000000014004ED88  lea         r8,[r8-40h] 
  000000014004ED8C  prefetchnta [rdx+180h] 
  000000014004ED93  movdqu      xmm0,xmmword ptr [rdx-40h] 
  000000014004ED98  movdqu      xmm1,xmmword ptr [rdx-30h] 
  000000014004ED9D  cmp         r8,40h 
  000000014004EDA1  movntdq     xmmword ptr [rcx-40h],xmm0 
  000000014004EDA6  movntdq     xmmword ptr [rcx-30h],xmm1 
  000000014004EDAB  movdqu      xmm2,xmmword ptr [rdx-20h] 
  000000014004EDB0  movdqu      xmm3,xmmword ptr [rdx-10h] 
  000000014004EDB5  movntdq     xmmword ptr [rcx-20h],xmm2 
  000000014004EDBA  movntdq     xmmword ptr [rcx-10h],xmm3 
  000000014004EDBF  jge         000000014004ED80 

und läuft bei ~ 2200 MByte / s.

Aber auf 32bit

  

memcpy(&dst[0],&src[0],dst.size())

Anrufe

  

__intel_fast_memcpy

der Großteil davon besteht aus

  004447A0  sub         ecx,80h 
  004447A6  movdqa      xmm0,xmmword ptr [esi] 
  004447AA  movdqa      xmm1,xmmword ptr [esi+10h] 
  004447AF  movdqa      xmmword ptr [edx],xmm0 
  004447B3  movdqa      xmmword ptr [edx+10h],xmm1 
  004447B8  movdqa      xmm2,xmmword ptr [esi+20h] 
  004447BD  movdqa      xmm3,xmmword ptr [esi+30h] 
  004447C2  movdqa      xmmword ptr [edx+20h],xmm2 
  004447C7  movdqa      xmmword ptr [edx+30h],xmm3 
  004447CC  movdqa      xmm4,xmmword ptr [esi+40h] 
  004447D1  movdqa      xmm5,xmmword ptr [esi+50h] 
  004447D6  movdqa      xmmword ptr [edx+40h],xmm4 
  004447DB  movdqa      xmmword ptr [edx+50h],xmm5 
  004447E0  movdqa      xmm6,xmmword ptr [esi+60h] 
  004447E5  movdqa      xmm7,xmmword ptr [esi+70h] 
  004447EA  add         esi,80h 
  004447F0  movdqa      xmmword ptr [edx+60h],xmm6 
  004447F5  movdqa      xmmword ptr [edx+70h],xmm7 
  004447FA  add         edx,80h 
  00444800  cmp         ecx,80h 
  00444806  jge         004447A0

und läuft bei ~ 1350 MByte / s nur.

JEDOCH

memcpy(&dst[0],&src[0],N)

wobei N vorher const size_t N=512*(1<<20); compiles deklariert wird (auf 32-Bit) auf einen direkten Aufruf zu einem

__intel_VEC_memcpy

der Großteil davon besteht aus

  0043FF40  movdqa      xmm0,xmmword ptr [esi] 
  0043FF44  movdqa      xmm1,xmmword ptr [esi+10h] 
  0043FF49  movdqa      xmm2,xmmword ptr [esi+20h] 
  0043FF4E  movdqa      xmm3,xmmword ptr [esi+30h] 
  0043FF53  movntdq     xmmword ptr [edi],xmm0 
  0043FF57  movntdq     xmmword ptr [edi+10h],xmm1 
  0043FF5C  movntdq     xmmword ptr [edi+20h],xmm2 
  0043FF61  movntdq     xmmword ptr [edi+30h],xmm3 
  0043FF66  movdqa      xmm4,xmmword ptr [esi+40h] 
  0043FF6B  movdqa      xmm5,xmmword ptr [esi+50h] 
  0043FF70  movdqa      xmm6,xmmword ptr [esi+60h] 
  0043FF75  movdqa      xmm7,xmmword ptr [esi+70h] 
  0043FF7A  movntdq     xmmword ptr [edi+40h],xmm4 
  0043FF7F  movntdq     xmmword ptr [edi+50h],xmm5 
  0043FF84  movntdq     xmmword ptr [edi+60h],xmm6 
  0043FF89  movntdq     xmmword ptr [edi+70h],xmm7 
  0043FF8E  lea         esi,[esi+80h] 
  0043FF94  lea         edi,[edi+80h] 
  0043FF9A  dec         ecx  
  0043FF9B  jne         ___intel_VEC_memcpy+244h (43FF40h) 

und läuft bei ~ 2100MByte / s (und 32bit erweist sich irgendwie nicht begrenzte Bandbreite).

hebe ich meine Behauptung, dass mein eigener Memcpy artige SSE-Code aus einer leidet ähnlich ~ 1300 MByte / Limit in 32bit baut; Ich jetzt habe keine Probleme immer> 2 GByte / s auf 32 oder 64 Bit; der Trick (wie die obigen Ergebnisse deuten) ist nicht-zeitliche ( "Streaming") speichert zu verwenden (z _mm_stream_ps intrinsisch).

Es scheint ein wenig seltsam, dass die 32-Bit „dst.size()“ Memcpy nicht schließlich rufen Sie die schnellen „movnt“ Version (wenn Sie in Memcpy Schritt gibt es die meisten unglaubliche Menge an CPUID Prüfung und heuristischer Logik z Anzahlvergleich von Bytes mit Cache-Größe usw. kopiert werden, bevor es irgendwo in der Nähe Ihrer geht Ist-Daten), aber zumindest verstehe ich das beobachtete Verhalten jetzt (und es ist nicht SysWow64 oder H / W bezogen).

Meine off-the-Manschette Vermutung ist, dass die 64-Bit-Prozesse des Prozessors native 64-Bit-Speichergröße verwenden, die die Verwendung des Speicherbusses optimiert.

Vielen Dank für das positive Feedback! Ich glaube, ich kann teilweise erklären, was hier vor sich geht.

, um die nicht-zeitlichen Geschäfte für memcpy verwenden ist definitiv das gefastet , wenn Sie nur den Memcpy Anruf Timing.

Auf der anderen Seite, wenn Sie eine Anwendung Benchmarking hat die MOVDQA speichern den Vorteil, dass sie den Zielspeicher im Cache lassen. Oder zumindest der Teil davon, die in der Cache paßt.

Wenn Sie also eine Laufzeitbibliothek sind die Gestaltung und wenn Sie davon ausgehen können, dass die Anwendung, die Memcpy genannt wird den Zielpuffer unmittelbar nach dem Memcpy Aufruf verwenden, dann werden Sie die MOVDQA Version zur Verfügung stellen mögen. Dies optimiert heraus effektiv die Reise aus dem Speicher zurück in die CPU, die die movntdq Version folgen würde, und alle Anweisungen des Anrufs folgende laufen schneller.

Aber auf der anderen Seite, wenn der Zielpuffer groß ist im Vergleich zu dem Cache des Prozessors, ist, dass die Optimierung nicht funktioniert und die movntdq Version würden Ihnen schneller Anwendungsbenchmarks geben.

So ist die Idee Memcpy mehrere Versionen unter der Haube hat. Wenn der Zielpuffer an den Cache des Prozessors klein im Vergleich ist, verwenden MOVDQA, sonst, so ist der Zielpuffer groß im Vergleich zu dem Cache des Prozessors, Verwendung movntdq. Es klingt wie das ist, was in der 32-Bit-Bibliothek passiert.

Natürlich nichts davon etwas mit den Unterschieden zwischen dem 32-Bit- und 64-Bit zu tun.

Meine Vermutung ist, dass die 64-Bit-Bibliothek gerade nicht so ausgereift ist. Die Entwickler haben einfach nicht dazu gekommen, doch beide Routinen in dieser Version der Bibliothek zu bieten.

Ich habe keine Referenz vor mir, also bin ich nicht unbedingt positiv auf die Timings / Anweisungen, aber ich kann immer noch die Theorie geben. Wenn Sie einen Speicher bewegen unter 32-Bit-Modus zu tun, werden Sie so etwas wie ein „rep movsd“ tun, die in jedem Taktzyklus einen einzelnen 32-Bit-Wert bewegt. Unter 64-Bit-Modus können Sie eine „rep movsq“ tun, die sich ein einzelner 64-Bit in jedem Taktzyklus bewegen. Diese Anweisung ist auf 32-Bit-Code nicht verfügbar ist, so würde man 2 x rep movsd tun (bei 1 Zyklus eines Stück) für die Hälfte der Ausführungsgeschwindigkeit.

sehr vereinfacht, ohne auf alle Speicherbandbreite / Ausrichtungsprobleme, usw., aber das ist, wo alles beginnt ...

Hier ist ein Beispiel für eine Memcpy Routine speziell für 64-Bit-Architektur ausgerichtet.

void uint8copy(void *dest, void *src, size_t n){
    uint64_t * ss = (uint64_t)src;
    uint64_t * dd = (uint64_t)dest;
    n = n * sizeof(uint8_t)/sizeof(uint64_t); 

    while(n--)
        *dd++ = *ss++;
}//end uint8copy()

Der vollständige Artikel ist hier: http://www.godlikemouse.com/2008/03/04/ Optimierung-memcpy-Routinen /

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top