Um dia, acidentalmente me deparei com um código informando que um usuário estava tentando monitorar o desempenho da RAM em sua máquina virtual. Não vou dar esse código (tem uma “calça” aí) e vou deixar apenas o mais essencial. Então, o gato está 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 - aloque memória e grave um gigabyte nela. E o que esse teste mostra?
$./memtest
4.06504 GB / s
Aproximadamente 4 GB/s.
O que?!?!
Como?!?!?
Este é o Core i7 (embora não seja o mais novo), DDR4, o processador quase não carrega - POR QUÊ?!?!
A resposta, como sempre, é extraordinariamente comum.
O operador new (como a função malloc, aliás) na verdade não aloca memória. Com esta chamada, o alocador olha a lista de locais livres no conjunto de memória e, se não houver nenhum, chama sbrk() para aumentar o segmento de dados e, em seguida, retorna ao programa uma referência ao endereço do novo local apenas alocado.
O problema é que a área alocada é totalmente virtual. Nenhuma página de memória real é alocada.
E quando ocorre o primeiro acesso a cada página deste segmento alocado, a MMU “dispara” uma falha de página, após a qual a página virtual acessada recebe uma página real.
Portanto, na verdade, não estamos testando o desempenho dos módulos de barramento e RAM, mas sim o desempenho do MMU e VMM do sistema operacional. E para testar o desempenho real da RAM, só precisamos inicializar as áreas alocadas uma vez. Por exemplo assim:
#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 o valor padrão (char 0).
Verificamos:
$./memtest
28.5714 GB / s
Outra coisa.
Moral da história: se você precisar de buffers grandes para funcionar rapidamente, não se esqueça de inicializá-los.
Fonte: habr.com