Még egy kis beszélgetés a helytelen tesztelésről

Egyszer véletlenül a szemem elé került egy kód, amellyel a felhasználó megpróbálta monitorozni a RAM teljesítményét a virtuális gépén. Ezt a kódot nem fogom bemutatni (ott »hosszú« van), és csak a legfontosabbat hagyom meg. Tehát, macska a stúdióban!

#include <sys/time.h>
#include <string.h>
#include <iostream>

#define CNT 1024
#define SIZE (1024*1024)

int main() {
	struct timeval start;
	struct timeval end;
	long millis;
	double gbs;
	char ** buffers;
	buffers = new char*[CNT];
	for (int i=0;i<CNT;i++) {
		buffers[i] = new char[SIZE];
	}
	gettimeofday(&start, NULL);
	for (int i=0;i<CNT;i++) {
		memset(buffers[i], 0, SIZE);
	}
	gettimeofday(&end, NULL);
	millis = (end.tv_sec - start.tv_sec) * 1000 +
		(end.tv_usec - start.tv_usec) / 1000;
	gbs = 1000.0 / millis;
	std::cout << gbs << " GB/sn";
	for (int i=0;i<CNT;i++) {
		delete buffers[i];
	}
	delete buffers;
	return 0;
}

Minden egyszerű — foglalunk memóriát, és írunk bele egy gigabájtnyi adatot. Mit mutat ez a teszt?

$ ./memtest
4.06504 GB/s

Körülbelül 4GB/s.

Mi?!?!

Hogyan?!?!?

Ez egy Core i7 (bár nem a legújabb), DDR4, a processzor szinte nincs terhelve — MIÉRT?!?!

A válasz, mint mindig, rendkívül banális.

A new operátor (és a malloc függvény is, mellesleg) valójában nem foglal memóriát. E hívás során az allokátor megnézi a memóriapoolban a szabad szegmensek listáját, és ha azok nincsenek, meghívja az sbrk()-t, hogy megnövelje az adat szegmenst, majd visszaadja a programnak a címre mutató hivatkozást az újonnan allokált szegmensből.

A probléma az, hogy az allokált szegmens teljesen virtuális. A tényleges memóriablokkok nincsenek allokálva.

És amikor az első hivatkozás történik az ezen allokált szegmensen belüli minden lapra, az MMU "ráüt" egy page fault-t, majd a virtuális lap, amelyhez hozzáférnek, egy valós lapot kap.

Ezért valójában nem a busz és a RAM modulok teljesítményét teszteljük, hanem az operációs rendszer MMU és VMM teljesítményét. Ahhoz, hogy teszteljük a tényleges RAM teljesítményt, egyszerűen csak inicializálnunk kell az allokált szegmenseket. Például így:

#include <sys/time.h>
#include <string.h>
#include <iostream>

#define CNT 1024
#define SIZE (1024*1024)

int main() {
	struct timeval start;
	struct timeval end;
	long millis;
	double gbs;
	char ** buffers;
	buffers = new char*[CNT];
	for (int i=0;i<CNT;i++) {
                // FIXED HERE!!!
		buffers[i] = new char[SIZE](); // Add brackets, &$# !!!
	}
	gettimeofday(&start, NULL);
	for (int i=0;i<CNT;i++) {
		memset(buffers[i], 0, SIZE);
	}
	gettimeofday(&end, NULL);
	millis = (end.tv_sec - start.tv_sec) * 1000 +
		(end.tv_usec - start.tv_usec) / 1000;
	gbs = 1000.0 / millis;
	std::cout << gbs << " GB/sn";
	for (int i=0;i<CNT;i++) {
		delete buffers[i];
	}
	delete buffers;
	return 0;
}

Tehát, egyszerűen az allokált puffereket alapértelmezett értékkel inicializáljuk (char 0).

Ellenőrizzük:

$ ./memtest
28.5714 GB/s

Ez más tészta.

Moral — ha nagy pufferekre van szüksége a gyors munkához, ne felejtse el azokat inicializálni.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster