PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Memcpy oder nicht



Andi_Rostock
18-05-2011, 07:48
Hallo zusammen,

ich habe da mal ne grundsätzliche Frage.
Wann benutzt man eigentlich memcpy?
Konkret geht's bei mir darum, dass ich eine double Matrix kopieren möchte.

Aktuell kopiere ich so...


double A[50][50];
double B[50][50];
int M =50;
for (i=0;i<M*M;i++){
B[i] = A[i];
}


Wäre da memcpy schneller?

Andi

jeebee
18-05-2011, 12:27
memcpy ist üblicherweise einiges schneller als ein "normaler" for-loop:

#include <stdio.h>
#include <string.h>
#include "cyc.h"

double a[50][50];
double b[50][50];

void do_memcpy() {
memcpy(b, a, 50*50*sizeof(double));
}

void do_loop() {
int elem_count = 50*50;
int i = 0;
double *A = &a[0][0];
double *B = &b[0][0];
for (i=0; i<elem_count;i++) {
B[i]=A[i];
}
}

int main() {
// init cycle counter
start_counter();

// init cache
double start_cyc = get_counter();
do_loop();
double warmup_time = get_counter() - start_cyc;

// measure loop performance
start_cyc = get_counter();
do_loop();
double loop_time = get_counter() - start_cyc;

// measure memcpy performance
start_cyc = get_counter();
do_memcpy();
double memcpy_time = get_counter() - start_cyc;

printf("Copying a 50x50 matrix of doubles:\n"
" using a for-loop: %.0lf cycles\n"
" using memcpy: %.0lf cycles\n",
loop_time, memcpy_time);

printf("\nCache warmup: %.0lf cycles\n", warmup_time);

return 0;
}

Resultate bei mir (Athlon X2 BE-2400, 512k cache):

Copying a 50x50 matrix of doubles:
using a for-loop: 47689 cycles
using memcpy: 17349 cycles

Cache warmup: 97446 cycles

Die Resultate ändern nur unwesentlich wenn zuerst die memcpy cycles und dann die for-loop cycles gemessen werden.

cycle counter implementation:

// cyc.h
#ifndef __CYC_H
#define __CYC_H

double get_counter();
void start_counter();

#endif

// cyc.c
static unsigned cyc_hi = 0;
static unsigned cyc_lo = 0;

void access_counter(unsigned *hi, unsigned *lo) {
/* Get cycle counter */
asm("rdtsc; movl %%edx, %0; movl %%eax, %1"
: "=r" (*hi), "=r" (*lo)
: /* No input */
: "%edx", "%eax");
}

double get_counter() {
unsigned ncyc_hi, ncyc_lo;
unsigned hi, lo, borrow;
/* Get cycle counter */
access_counter(&ncyc_hi, &ncyc_lo);
/* 64 bit subtraction */
lo = ncyc_lo - cyc_lo;
borrow = lo > ncyc_lo;
hi = ncyc_hi - cyc_hi - borrow;
return (double) hi * (1 << 30) * 4 + lo;
}

void start_counter() {
/* Get current value */
access_counter(&cyc_hi, &cyc_lo);
return;
}

locus vivendi
18-05-2011, 14:47
Wann benutzt man eigentlich memcpy?
Wenn man einen Block von Bytes kopieren möchte, wobei das Ziel nicht mit dem Original überlappt.

double A[50][50];
double B[50][50];
int M =50;
for (i=0;i<M*M;i++){
B[i] = A[i];
}
Es gibt Zweifel daran, dass deine Schleife korrekt ist. Du indizierst A und B außerhalb ihrer Grenzen. Dass ist nicht erlaubt. Es gibt zwar eine Regel die besagt, dass Array Elemente "dicht gepackt" werden, dennoch gibt es umfangreiche Diskussionen darüber ob damit die erste Regel außer Kraft gesetzt wird.

locus vivendi
18-05-2011, 19:31
double A[50][50];
double B[50][50];
int M =50;
for (i=0;i<M*M;i++){
B[i] = A[i];
}
Das ist natürlich noch falscher als mir vorhin aufgefallen ist... erstens kompiliert es nicht und zweitens, wenn es das täte würde es *auf jeden Fall* die Arraygrenzen verletzen.

Andi_Rostock
20-05-2011, 13:02
Oh, da habe ich wohl einige Fehler beim Erstellen meines Beispiels gemacht. Also,
hier ist das Original, wobei ich ein Matlab-Mex-File (C-API von Matlab) erstellt habe.
Die hier verwendeten Datentypen haben immer den Anhang "_T", damit Matlab Plattformübergreifend kompilieren kann.


int M;
real_T *A;
real_T *B;

M = 50;
A=mxCalloc((M*M), sizeof(real_T)); // analog zu calloc
B=mxCalloc((M*M), sizeof(real_T));

// Daten fuellen
for (i=0;i<(M*M);i++){
A[i] = 1.0/i;
}

// Nun das eigentliche kopieren was ich durch memcpy ersetzen möchte
for (i=0;i<(M*M);i++){
B[i] = A[i];
}
mxFree(A);
mxFree(B);



Hoffe das funz nun.
Andi

locus vivendi
20-05-2011, 13:34
Oh, da habe ich wohl einige Fehler beim Erstellen meines Beispiels gemacht. Also,
hier ist das Original, [...]


int M;
real_T *A;
real_T *B;

M = 50;
A=mxCalloc((M*M), sizeof(real_T)); // analog zu calloc
B=mxCalloc((M*M), sizeof(real_T));

// Daten fuellen
for (i=0;i<(M*M);i++){
A[i] = 1.0/i;
}

// Nun das eigentliche kopieren was ich durch memcpy ersetzen möchte
for (i=0;i<(M*M);i++){
B[i] = A[i];
}


Das ist in der Tat deutlich anders, weil du jetzt keine deklarierten Arrays, sondern angeforderten Speicher hast. Und soweit ist das auch korrekt. Allerdings hast du hier auch noch eine Division durch Null drin. Die muss raus.

Andi_Rostock
31-05-2011, 15:15
Erstmal sorry für die sehr verspätete Antwort. Ich war letzte Woche auf Reisen, daher konnte ich nicht weiter nachschauen.
Natürlich hast Du Recht. Die Division durch Null darf nicht sein. Es war aber nur Teil des hier vorgestellten Beispiels. Ich korrigiere es nun zu:


int M;
real_T *A;
real_T *B;

M = 50;
A=mxCalloc((M*M), sizeof(real_T)); // analog zu calloc
B=mxCalloc((M*M), sizeof(real_T));

// Daten fuellen
for (i=0;i<(M*M);i++){
A[i] = 1.0/(i*0.5+1);
}

// Nun das eigentliche kopieren was ich durch memcpy ersetzen möchte
for (i=0;i<(M*M);i++){
B[i] = A[i];
}
mxFree(A);
mxFree(B);


Hoffentlich stimmts nun. Aber ... bzgl. meiner Ausgangsfrage: Ist memcpy nun besser oder net?

jeebee
01-06-2011, 09:35
siehe post #2 für code.

Resultate bei mir (Athlon X2 BE-2400, 512k cache):

gcc -o memcpy_test memcpy_test.c cyc.c
Copying a 50x50 matrix of doubles:
using a for-loop: 47689 cycles
using memcpy: 17349 cycles

Cache warmup: 97446 cycles
-------
gcc -O1 -o memcpy_test memcpy_test.c cyc.c
Copying a 50x50 matrix of doubles:
using a for-loop: 26581 cycles
using a 4way for-loop: 23944 cycles
using memcpy: 17232 cycles

Cache warmup: 76411 cycles
-------
gcc -O2 -o memcpy_test memcpy_test.c cyc.c
Copying a 50x50 matrix of doubles:
using a for-loop: 19049 cycles
using a 4way for-loop: 12077 cycles
using memcpy: 17602 cycles

Cache warmup: 125178 cycles
-------
gcc -O3 -o memcpy_test memcpy_test.c cyc.c
Copying a 50x50 matrix of doubles:
using a for-loop: 14227 cycles
using a 4way for-loop: 13680 cycles
using memcpy: 16912 cycles

Cache warmup: 66657 cycles


--> for-loop ist optimiert schneller...

Andi_Rostock
01-06-2011, 11:30
Vielen Dank für deine Bemühungen!!
Also kann ich die For-Loops eigentlich stehen lassen.
Kannst Du evtl. auch mal für größere Matrizen testen? Vielleicht mal 1000x1000? Nur rein des Interesses wegen.

jeebee
01-06-2011, 12:45
kannst das ja eigentlich auch selbst, der Code ist ja in Post #2 komplett vorhanden...

edit: Nur meine Schlussfolgerung war falsch, da ich nicht daran dachte die Optimierungen einzuschalten.

Andi_Rostock
01-06-2011, 13:53
Ja, habs gerade schnell getestet. Also ab einer Matrix-Größe oberhalb von 500x500 Elementen ist memcpy schneller.
Das ist doch mal ne Aussage!!!

Vielen Dank jeebee,
Andi