Pak më shumë rreth testimit të keq

Një ditë rastësisht hasa në kodin që një përdorues po përpiqej të monitoronte performancën e RAM-it në makinën e tij virtuale. Unë nuk do ta jap këtë kod (ka një "rrobë këmbësh" atje) dhe do të lë vetëm më thelbësoren. Pra, macja është në studio!

#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;
}

Është e thjeshtë - ndani memorie dhe shkruani një gigabajt në të. Dhe çfarë tregon ky test?

$ ./memtest
4.06504 GB / s

Përafërsisht 4 GB/s.

Çfarë?!?!

Si?!?!?

Ky është Core i7 (megjithëse jo më i riu), DDR4, procesori pothuajse nuk është i ngarkuar - PSE?!?!

Përgjigja, si gjithmonë, është jashtëzakonisht e zakonshme.

Operatori i ri (si funksioni malloc, meqë ra fjala) në fakt nuk shpërndan memorie. Me këtë thirrje, alokuesi shikon listën e vendndodhjeve të lira në grupin e memories, dhe nëse nuk ka asnjë, thërret sbrk() për të rritur segmentin e të dhënave dhe më pas i kthen programit një referencë për adresën nga vendndodhja e re. të ndara.

Problemi është se zona e ndarë është tërësisht virtuale. Nuk ndahen faqe reale të memories.

Dhe kur ndodh qasja e parë në secilën faqe nga ky segment i caktuar, MMU "shpjek" një gabim faqeje, pas së cilës faqja virtuale që aksesohet i caktohet një e vërtetë.

Prandaj, në fakt, ne nuk po testojmë performancën e moduleve të autobusit dhe RAM-it, por performancën e MMU dhe VMM të sistemit operativ. Dhe për të testuar performancën reale të RAM-it, thjesht duhet të inicializojmë zonat e alokuara një herë. Për shembull si kjo:

#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;
}

Kjo do të thotë, ne thjesht inicializojmë buferët e alokuar me vlerën e paracaktuar (char 0).

kontrolloni:

$ ./memtest
28.5714 GB / s

Një tjetër gjë.

Morali i historisë - nëse keni nevojë për buferë të mëdhenj për të punuar shpejt, mos harroni t'i inicializoni ato.

Burimi: www.habr.com

Shto një koment