Se eu usar malloc
, malloc
sempre usa o mesmo algoritmo, independentemente do que está alocando, ou olha os dados e seleciona um algoritmo apropriado?
Podemos tornar o malloc mais rápido ou mais inteligente escolhendo um algoritmo mais eficiente? Nos meus testes, o sistema oficial interno malloc
do Ubuntu é 10 vezes mais lento que um projeto escolar, se os resultados dos meus testes estiverem corretos. Qual é o problema? Estou surpreso que o malloc
desempenho tenha sido tão ruim nos testes, porque deve ser otimizado. Ele sempre usa o mesmo algoritmo? Existe uma implementação de referência malloc
? Se eu quiser procurar a fonte malloc
, para quem devo procurar? Os testes que eu executo são os seguintes:
/* returns an array of arrays of char*, all of which NULL */
char ***alloc_matrix(unsigned rows, unsigned columns) {
char ***matrix = malloc(rows * sizeof(char **));
unsigned row = 0;
unsigned column = 0;
if (!matrix) abort();
for (row = 0; row < rows; row++) {
matrix[row] = calloc(columns, sizeof(char *));
if (!matrix[row]) abort();
for (column = 0; column < columns; column++) {
matrix[row][column] = NULL;
}
}
return matrix;
}
/* deallocates an array of arrays of char*, calling free() on each */
void free_matrix(char ***matrix, unsigned rows, unsigned columns) {
unsigned row = 0;
unsigned column = 0;
for (row = 0; row < rows; row++) {
for (column = 0; column < columns; column++) {
/* printf("column %d row %d\n", column, row);*/
free(matrix[row][column]);
}
free(matrix[row]);
}
free(matrix);
}
int main(int agrc, char **argv) {
int x = 10000;
char *** matrix = alloc_matrix(x, x);
free_matrix(matrix, x, x);
return (0);
}
O teste está bom? Eu também uso este teste:
for (i = 0; i < 1000000; i++) {
void *p = malloc(1024 * 1024 * 1024);
free(p);
}
- atualizar
De acordo com o comentário, eu devo fazer pedaços de tamanhos variados e livres em ordem diferente da alocação, então tento:
int main(int agrc, char **argv) {
int i;
srand(time(NULL));
int randomnumber;
int size = 1024;
void *p[size];
for (i = 0; i < size; i++) {
randomnumber = rand() % 10;
p[i] = malloc(1024 * 1024 * randomnumber);
}
for (i = size-1; i >= 0; i--) {
free(p[i]);
}
int x = 1024;
char *** matrix = alloc_matrix(x, x);
free_matrix(matrix, x, x);
return (0);
}
Então meu malloc personalizado não é mais rápido:
$ time ./gb_quickfit
real 0m0.154s
user 0m0.008s
sys 0m0.144s
dac@dac-Latitude-E7450:~/ClionProjects/omalloc/openmalloc/overhead$ time ./a.out
real 0m0.014s
user 0m0.008s
sys 0m0.004s
O algoritmo que usei foi:
void *malloc_quick(size_t nbytes) {
Header *moreroce(unsigned);
int index, i;
index = qindex(nbytes);
/*
* Use another strategy for too large allocations. We want the allocation
* to be quick, so use malloc_first().
*/
if (index >= NRQUICKLISTS) {
return malloc_first(nbytes);
}
/* Initialize the quick fit lists if this is the first run. */
if (first_run) {
for (i = 0; i < NRQUICKLISTS; ++i) {
quick_fit_lists[i] = NULL;
}
first_run = false;
}
/*
* If the quick fit list pointer is NULL, then there are no free memory
* blocks present, so we will have to create some before continuing.
*/
if (quick_fit_lists[index] == NULL) {
Header* new_quick_fit_list = init_quick_fit_list(index);
if (new_quick_fit_list == NULL) {
return NULL;
} else {
quick_fit_lists[index] = new_quick_fit_list;
}
}
/*
* Now that we know there is at least one free quick fit memory block,
* let's use return that and also update the quick fit list pointer so that
* it points to the next in the list.
*/
void* pointer_to_return = (void *)(quick_fit_lists[index] + 1);
quick_fit_lists[index] = quick_fit_lists[index]->s.ptr;
/* printf("Time taken %d seconds %d milliseconds", msec/1000, msec%1000);*/
return pointer_to_return;
}
fonte
sbrk
(ou o que os alocadores modernos usam).calloc
e depois explicitamente claro?malloc
é mais lento. Isso é o que eu esperaria.Respostas:
Existem várias implementações
malloc
e elas podem usar algoritmos muito diferentes. Duas implementações muito usadas são jemalloc e dlmalloc . Nos dois casos, os links têm muitas informações sobre o processo de reflexão e as trocas feitas em um alocador de uso geral.Lembre-se de que uma
malloc
implementação tem muito pouca informação, apenas o tamanho da alocação solicitada. Umafree
implementação possui apenas o ponteiro e quaisquer dados que 'malloc' possam ter secretamente anexado a ele. Como tal, acaba havendo uma quantidade razoável de heurísticas, isto é, suposições inspiradas ao decidir como alocar e desalocar.Alguns problemas que um alocador precisa resolver são:
Levando tudo isso em consideração, o que você encontrará é que os alocadores tendem a ser peças complexas de software para que, em uso geral, tenham um bom desempenho. No entanto, em casos específicos, pode ser melhor gerenciar a memória fora do alocador se você souber muito mais sobre a estrutura dos dados. Obviamente, a desvantagem é que você precisa fazer o trabalho sozinho.
fonte
valloc
que eu nunca ouvi falar antes, preciso verificar o que é.valloc
retorna a memória alinhada ao tamanho da página. Está obsoleto, como você pode usarmemalign
para isso.Se você se preocupa apenas com eficiência, aqui está uma implementação em conformidade padrão e muito eficiente :
Tenho certeza que você não encontrará mais rápido
malloc
.Mas, embora ainda esteja em conformidade com o padrão, essa implementação é inútil (nunca aloca com êxito nenhuma memória heap). É uma implementação de piada.
Isso mostra que no mundo real,
malloc
efree
implementações precisa fazer trade-offs . E várias implementações estão fazendo várias compensações. Você encontrará muitos tutoriais sobre implementações de malloc . Para depurar vazamentos de memória em seus programas em C, convém usar o valgrind .Observe que, nos sistemas Linux, pelo menos, (quase) todas as bibliotecas padrão C são software livre , para que você possa estudar o código fonte (em particular o código para
malloc
&free
). musl-libc tem algum código fonte muito legível.BTW, o código na sua pergunta está errado (e falhará com o meu
malloc
acima). Toda chamada paramalloc
pode falhar, e você deve testar isso.Você pode ler a Programação Avançada do Linux e algum material mais geral sobre sistemas operacionais, por exemplo, Sistemas Operacionais: três partes fáceis .
Você provavelmente deve ler algo sobre coleta de lixo , pelo menos para obter os principais conceitos e terminologia; talvez lendo o manual do GC . Observe que a contagem de referência pode ser vista como uma forma de GC (baixa, que não lida com ciclos de referência ou gráficos cíclicos ...).
Você pode querer usar no coletor de lixo conservador do programa C de Boehm : você usaria
GC_MALLOC
(ou, para dados sem ponteiros como seqüências de caracteres ou matrizes numéricasGC_MALLOC_ATOMIC
) , em vez demalloc
e não se preocupará em ligarfree
mais. Existem algumas advertências ao usar o GC da Boehm. Você pode considerar outros esquemas ou bibliotecas de GC ...NB: Para testar a falha do malloc em um sistema Linux (
malloc
às vezes chama o sistema mmap (2) e / ou sbrk (2) , o Linux deve aumentar o espaço de endereço virtual , mas na maioria das vezes ele tenta reutilizar afree
memória d anteriormente ) , você pode chamar o setrlimit (2) adequadamente comRLIMIT_AS
e / ouRLIMIT_DATA
, geralmente através doulimit
bash embutido no shell do terminal. Use strace (1) para descobrir as chamadas do sistema feitas pelo seu (ou algum outro) programa.fonte
malloc
estão retornando umNULL
ponteiro (não ) que não pôde ser desreferenciado.Primeiro, malloc e trabalho livre juntos, portanto, testar o malloc por si só é enganador. Segundo, não importa quão bons sejam, eles podem facilmente ser o custo dominante em qualquer aplicativo, e a melhor solução para isso é chamá-los menos. Chamar menos é quase sempre a maneira vencedora de consertar programas limitados ao malloc . Uma maneira comum de fazer isso é reciclar objetos usados. Quando você é feito com um bloco, ao invés de livre -ing-lo, você encadear-lo em uma pilha de blocos usados e re uso-lo na próxima vez que você precisar de um.
fonte
O principal problema com sua
malloc_quick()
implementação é que ela não é segura para threads. E sim, se você omitir o suporte a threads do seu alocador, poderá obter um ganho de desempenho significativo. Vi uma aceleração semelhante com meu próprio alocador não thread-safe.No entanto, uma implementação padrão precisa ser segura para threads. Ele precisa levar em conta todos os seguintes itens:
Threads diferentes usam
malloc()
efree()
simultaneamente. Isso significa que a implementação não pode acessar o estado global sem sincronização interna.Como os bloqueios são muito caros, as
malloc()
implementações típicas tentam evitar o máximo possível de estado global, usando armazenamento local de encadeamento para quase todas as solicitações.Um segmento que aloca um ponteiro não é necessariamente o segmento que o libera. Isso tem que ser resolvido.
Um thread pode alocar memória constantemente e passá-lo para outro thread para liberá-lo. Isso torna o manuseio do último ponto muito mais difícil, porque significa que blocos livres podem se acumular em qualquer armazenamento local de encadeamento. Isso força a
malloc()
implementação a fornecer meios para os encadeamentos trocarem blocos livres e provavelmente requer a captura de bloqueios de tempos em tempos.Se você não se importa com os encadeamentos, todos esses pontos não apresentam problemas; portanto, um alocador que não seja seguro para encadeamentos não precisará pagar pelo tratamento com desempenho. Mas, como eu disse, uma implementação padrão não pode ignorar esses problemas e, consequentemente, tem que pagar por seu tratamento.
fonte
Eu acho que os dois SUT não são comparações diretas. Eu não ficaria surpreso com nenhuma diferença comparável quando você considera todas as variáveis: fabricação de memória, arquitetura da placa-mãe, versão do compilador (que compilou malloc), como é a aplicação do espaço de memória no SUT na época, etc etc etc ... .... Tente usar o seu equipamento de teste para ser mais representativo de como você usaria a memória em um projeto real - com algumas alocações grandes / pequenas e alguns aplicativos mantidos por um longo período de tempo e alguns liberados logo após serem executados.
fonte
Se você comparar uma implementação de malloc real com um projeto de escola, considere que um malloc real precisa gerenciar alocações, realocações e liberar memória de tamanhos enormemente diferentes, funcionando corretamente se as alocações ocorrerem em encadeamentos diferentes simultaneamente e a memória de realocação e liberação ocorrer em encadeamentos diferentes . Você também quer ter certeza de que qualquer tentativa de liberar memória que não foi alocada com o malloc seja detectada. E, finalmente, certifique-se de não comparar com uma implementação de malloc de depuração.
fonte