Um pouco mais sobre testes inadequados

Certa vez, por acaso, eu vi um código que um usuário estava tentando usar para monitorar o desempenho da RAM em sua máquina virtual. Não vou apresentar esse código (ele é um «monte»), mas deixarei apenas o mais importante. Então, um gato no estúdio!

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

É simples — alocamos memória e escrevemos um gigabyte nela. E o que esse teste mostra?

$ ./memtest
4.06504 GB/s

Cerca de 4GB/s.

O quê?!?!

Como?!?!?

É um Core i7 (embora não seja o mais novo), DDR4, o processador está quase sem carga — POR QUÊ?!?!

A resposta, como sempre, é extraordinariamente comum.

O operador new (assim como a função malloc, por sinal) na verdade não aloca memória. Neste chamado, o alocador verifica a lista de segmentos livres no pool de memória e, se não houver, chama sbrk() para aumentar o segmento de dados, e em seguida retorna à programa uma referência ao endereço do novo segmento que acabou de ser alocado.

O problema é que a área alocada é totalmente virtual. As páginas reais de memória não foram alocadas.

E quando ocorre o primeiro acesso a cada página deste segmento alocado, a MMU ‘dispara’ uma page fault, após a qual a página virtual que está sendo acessada recebe uma página real.

Portanto, na verdade, não estamos testando o desempenho do barramento e dos módulos de RAM, mas sim o desempenho da MMU e do VMM do sistema operacional. E para testar o desempenho real da memória RAM, precisamos apenas inicializar os segmentos alocados uma vez. Por exemplo:

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

Ou seja, simplesmente inicializamos os buffers alocados com um valor padrão (char 0).

Verificamos:

$ ./memtest
28.5714 GB/s

Isso faz diferença.

A moral — se você precisa de buffers grandes para trabalhar rapidamente, não se esqueça de inicializá-los.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster