Geschwindigkeitsunterschied Vector - Array

Hallo zusammen,

ich habe eine Frage zur Implementierung einerseits vom Containertyp „vector“ und andererseits von Arrays.
Und zwar habe ich folgende Programmabschnitte:


// ITERIEREN VECTOR
iter_t_vector = t_vector.begin();
groesse = 100000;
start = HRTimeInSec();
for (i=0; i size=100;
}
end = HRTimeInSec();

und

// ITERIEREN Arr []
groesse = 100000;
start = HRTimeInSec();
for (i=0; i

Hallo Steffen

iter_t_vector = t_vector.begin();
groesse = 100000;
start = HRTimeInSec();
for (i=0; i size=100;
}

entspricht:

 \*data\_ptr++ = 100;

groesse = 100000;
start = HRTimeInSec();
for (i=0; i

data_ptr = data_addr + i * sizeof(DATA_ENTRY);
*data_ptr = 100;

Jetzt würde mich interessieren, wieso das Iterieren durch den
Vector schneller vonstatten geht, als das Iterieren durch das
Array. Beide sind vom selben Datentyp und das Ergebnis ist bei
verschieden Compilern (Borland, MS, GNU) immer das Selbe. Der
Geschwindigkeitsvorteil des Vektors liegt bei ca. 10%.
Kann mir jemand eine Erklärung hierfür geben?

Ja, siehe oben.

Schreibe noch einen dritten Block mit:

 t\_arr\_type \*ptr = t\_arr;
 groesse = 100000;
 for (i=0; isize=100;
 }

und teste diesen.

Grüße

CMБ

Hallo Steffen,

Semjon hat ja schon alles richtig erklärt, du sparst dir eine Multiplikation.

Noch ein Tip von mir:

In solchen Fällen ist es Hilfreich sich den Assembler-Code anzusehen, welcher dein Compiler erzeugt (Dazu gibt es bei jedem Compiler eine Option, ein Assembler-Listing zu erzeugen).

Du lernst dabei was die CPU eiegntlich so alles machen muss und wo deine Rechenzeit abbleibt.

MfG Peter(TOO)

Hallo,

erstmal danke für die Hilfe. Der Geschwindigkeitsunterschied ist mir somit klar geworden.

Wenn ich allerdings den von dir vorgeschlagenen Block ausführe erreiche ich die selbe Laufzeit (minimal geringer) wie beim []-Operator. Der Vector ist somit auch hier schneller. Dies kann ich mit deiner Erklärung nicht nachvollziehen, da der Pointer doch nun tatsächlich direkt auf das aktuelle Element des Array zeigen sollte und die Multiplikation somit entfällt oder sehe ich das falsch?

Vielen Dank nochmal

Steffen

[Bei dieser Antwort wurde das Vollzitat nachträglich automatisiert entfernt]

Hallo Steffen,

Wenn ich allerdings den von dir vorgeschlagenen Block ausführe
erreiche ich die selbe Laufzeit (minimal geringer) wie beim
[]-Operator. Der Vector ist somit auch hier schneller. Dies
kann ich mit deiner Erklärung nicht nachvollziehen, da der
Pointer doch nun tatsächlich direkt auf das aktuelle Element
des Array zeigen sollte und die Multiplikation somit entfällt
oder sehe ich das falsch?

Das kann am „Layout“ des Datensatzes liegen,
vielleicht legt „vector“ die Startadresse
des Arrays so, dass die relevanten Daten auf
einer durch 64 teilbaren Adresse liegen, bei
der „normalen“ Allokation ist das dann nicht
oder nur zufällig gegeben. Das kommt aber
auf die Zusammensetzung Deines Datensatzes
an.

Kurz gesagt, wenn ein Datenelement so
liegt, dass es „im Mittel“ öfters eine
‚cache-line‘ am Anfang oder am Ende „schneidet“,
führt das zu ‚cache-trashing‘, die Performance
bricht ein. Man hat also ein ungünstiges „aliasing“
gegen die „cache boundaries“, simpel ausgedrückt :wink:

Kannst Du genauere Angaben zu Deinem Test-
programm machen?

Grüße

CMБ

Hallo nochmal,

und auch gleich nochmal Danke für deine hilfreiche Antwort.

Hier mal die relevanten Ecken meines Programms:

class MyClass {
public:
int x, y, size;
unsigned char gw;

MyClass() {
x = (rand() % 1024);
y = (rand() % 768);
size = (rand() % 400);
gw = (rand() % 255);
}

};

MyClass einfuege_objekt;
for (i = 0; i size=100;
}
end = HRTimeInSec();

start = HRTimeInSec();
for (i=0; i size = 100;
}
end = HRTimeInSec();

Ich habe das Programm jetzt mit folgenden Compilern übersetzt:
Borland 6, VS 6, VS.net, VS 2005, GNU (DEV-C++)
Hie die Ergebnisser der Zeitmessungen:
Iterieren Vector:
VS.net: 0,92ms
VS 6: 0,84ms
VS 2005: 0,91ms
Borland: 0,95ms
GNU: 1,40ms

Iterieren Array []:
VS.net: 1,11ms
VS 6: 1,00ms
VS 2005: 1,00ms
Borland: 1,02ms
GNU: 0,99ms

Iterieren Array *:
VS.net: 1,08ms
VS 6: 1,00ms
VS 2005: 0,97ms
Borland: 1,07ms
GNU: 0,98ms

Man sieht ja recht deutlich, dass die beiden Messungen mit dem Array nahezu identische Ergebnisse liefern, während der Vector (abgesehen vom GNU-Compiler) immer langsamer ist, was gegen die Theorie spricht, dass der []-Operator eine Operation mehr durchführen muss, oder?

Ich kann dir auch gerne mal den kompletten Quellcode per Mail zusende.

Vielen Dank
Steffen

Das kann am „Layout“ des Datensatzes liegen,
vielleicht legt „vector“ die Startadresse
des Arrays so, dass die relevanten Daten auf
einer durch 64 teilbaren Adresse liegen, bei
der „normalen“ Allokation ist das dann nicht
oder nur zufällig gegeben. Das kommt aber
auf die Zusammensetzung Deines Datensatzes
an.

Kurz gesagt, wenn ein Datenelement so
liegt, dass es „im Mittel“ öfters eine
‚cache-line‘ am Anfang oder am Ende „schneidet“,
führt das zu ‚cache-trashing‘, die Performance
bricht ein. Man hat also ein ungünstiges „aliasing“
gegen die „cache boundaries“, simpel ausgedrückt :wink:

Kannst Du genauere Angaben zu Deinem Test-
programm machen?

Grüße

CMБ

Hallo Steffen

Hier die Ergebnisser der Zeitmessungen:
Iterieren Vector:
VS.net: 0,92ms
VS 6: 0,84ms
VS 2005: 0,91ms
Borland: 0,95ms
GNU: 1,40ms

Iterieren Array []:
VS.net: 1,11ms
VS 6: 1,00ms
VS 2005: 1,00ms
Borland: 1,02ms
GNU: 0,99ms

Iterieren Array *:
VS.net: 1,08ms
VS 6: 1,00ms
VS 2005: 0,97ms
Borland: 1,07ms
GNU: 0,98ms

Man sieht ja recht deutlich, dass die beiden Messungen mit dem
Array nahezu identische Ergebnisse liefern, während der Vector
(abgesehen vom GNU-Compiler) immer langsamer ist, was gegen
die Theorie spricht, dass der []-Operator eine Operation mehr
durchführen muss, oder?

Ich denke mal, das ist ausschliesslich ein
Problem der Speicherverwaltung. Alle Operationen
„drum herum“ spielen hier kaum eine Rolle, ausser
dass u.U. bei ein geringer Overhead exis-
tieren kann. geringe Laufzeitunterscheide dürften
reiner Zufall sein und sich ggf. bei verschiedenen
Läufen ändern. Möglicherweise kannst Du noch ein
paar (lokale) Berechnungen einflechten, ohne dass
sich die Laufzeit ändert.

Ich hab mal die Quell-Fragmente hergenommen,
die Blockgröße erhöht und ein paar Tests gemacht.
Je nach Compiler wird statistisch eher das
eine oder eher das andere „etwas“ schneller
sein. Signifikant ist das wahrscheinlich nicht.

Das Alignment spielt offenbar keine Rolle, alle
Compiler machen ein „padding“ des char-Members
auf 32bit.

Beispiel:

groesse = 10_000_000 (==> 2 x 160MB data blocks)

Visual C++/6 SP5 -Oxt

 VECTOR : 406994181 cpu cycles,
 ARRAY : 402805987 cpu cycles,
 POINTER: 401981090 cpu cycles

gcc 3.4.1, -O3

 VECTOR : 404375218 cpu cycles,
 ARRAY : 408711841 cpu cycles,
 POINTER: 431653670 cpu cycles

gcc 4.0.2 -O3

 VECTOR : 442514272 cpu cycles,
 ARRAY : 432433397 cpu cycles,
 POINTER: 404922653 cpu cycles

Die „Zeitmessung“ habe ich mit dem
‚time stamp counter‘ des Prozessors
vorgenommen, er gibt die Anzahl der
‚raw‘-Zyklen an (CPU: Athlon-64/3400)

Hier der Quelltext, den ich verwendet habe:

#include "rdtsc.h" // for tsc\_ticks()
#include 
#include 
#include 

 class MyClass {
 public:
 int size, x, y;
 unsigned char gw;
 MyClass() {
 x = (rand() % 1024);
 y = (rand() % 768);
 size = (rand() % 400);
 gw = (rand() % 255);
 }
 };

 int proc(QWORD& ve\_end, QWORD& ar\_end, QWORD& pt\_end)
{
 unsigned long i, groesse = 10000000;
 MyClass einfuege\_objekt;

 std::vector t\_vector;
 std::vector::iterator iter\_t\_vector;

 MyClass \*t\_arr = new MyClass[groesse];
 MyClass \*Pointer = t\_arr;

 QWORD start; // QWORD is "unsigned long long" or "\_\_int64" 

 t\_vector.reserve(groesse);
 for (i=0; isize = 100;
 }
 ve\_end = tsc\_ticks() - start;

 // - - - - - - do array test
 start = tsc\_ticks();
 for (i=0; isize = 100;
 }
 pt\_end = tsc\_ticks() - start;

 // make sure the loop won't be 'optimized away':
 return Pointer-\>size + t\_arr[0].size + iter\_t\_vector-\>size;
}

 int main()
{
 QWORD ve\_end, ar\_end, pt\_end;
 proc(ve\_end, ar\_end, pt\_end);
 printf( "\nsizeof(MyClass): %i bytes \n\n"
 "\nVECTOR : %8u cycles,"
 "\nARRAY : %8u cycles,"
 "\nPOINTER: %8u cycles\n",
 sizeof(MyClass), 
 (long)ve\_end, (long)ar\_end, (long)pt\_end);

 return 0;
}

Die Funktion tsc_ticks() sähe dann so aus:

 QWORD tsc\_ticks() 
{
 #ifdef \_MSC\_VER
 // disable "no return value" warning
 #pragma warning( disable : 4035 )
 \_asm xor eax, eax
 \_asm xor edx, edx
 \_asm rdtsc // \_asm \_emit 0x0f // \_asm \_emit 0x31
 #else
 // standard gnu inline assembly version
 \_\_asm\_\_ (
 "xorl %eax, %eax\n"
 "xorl %edx, %edx\n"
 "rdtsc"); 
 #endif
}

Grüße

CMБ

1 Like

Hallo,

vielen Dank für die Mühe die du dir gegeben hast. Das hat mir alles sehr viel weiter geholfen.

Grüße
Steffen