Iom pli pri malbonaj provoj

Iun tagon mi hazarde trovis kodon, ke uzanto provis kontroli RAM-agadon en sia virtuala maŝino. Mi ne donos ĉi tiun kodon (estas "piedtuko" tie) kaj mi lasos nur la plej esencajn. Do, la kato estas en la 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;
}

Ĝi estas simpla - asignu memoron kaj skribu unu gigabajton en ĝi. Kaj kion montras ĉi tiu provo?

$ ./memtesto
4.06504 GB / s

Proksimume 4GB/s.

Kio?!?!

Kiel?!?!?

Ĉi tio estas Core i7 (kvankam ne la plej nova), DDR4, la procesoro preskaŭ ne estas ŝarĝita - KIAL?!?!

La respondo, kiel ĉiam, estas nekutime ordinara.

La nova funkciigisto (kiel la malloc-funkcio, cetere) ne efektive asignas memoron. Kun ĉi tiu alvoko, la alsignilo rigardas la liston de liberaj lokoj en la memorgrupo, kaj se ne ekzistas, vokas sbrk() por pliigi la datumsegmenton, kaj poste redonas al la programo referencon al la adreso de la nova loko nur asignita.

La problemo estas, ke la asignita areo estas tute virtuala. Neniuj veraj memorpaĝoj estas asignitaj.

Kaj kiam okazas la unua aliro al ĉiu paĝo de ĉi tiu asignita segmento, la MMU "pafas" paĝan misfunkciadon, post kiu la virtuala paĝo alirebla ricevas realan.

Tial, fakte, ni ne provas la agadon de la buso kaj RAM-moduloj, sed la agado de la MMU kaj VMM de la operaciumo. Kaj por testi la realan agadon de RAM, ni nur bezonas pravalorigi la asignitajn areojn unufoje. Ekzemple tiel:

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

Tio estas, ni simple pravalorigas la asignitajn bufrojn kun la defaŭlta valoro (char 0).

Ni kontrolas:

$ ./memtesto
28.5714 GB / s

Alia afero.

Moralo de la rakonto - se vi bezonas grandajn bufrojn por funkcii rapide, ne forgesu pravalorigi ilin.

fonto: www.habr.com

Aldoni komenton