Date: Sat, 22 Jul 1995 15:06:25 -0700 (PDT) From: Poul-Henning Kamp <phk> To: hackers@freebsd.org Subject: Please try this new malloc(3) Message-ID: <199507222206.PAA06101@freefall.cdrom.com>
next in thread | raw e-mail | index | archive | help
Hi Gang, Here is a new (old actually) malloc that I have made. I belive it is fully working and functional. The performance seems to be comparable to the gnu malloc, (generally marginally better I think :-) and the copyright is a lot more permissive. In particular I'm interested in hearing what it does to a X-server, and any problems it causes there or other places. It doesn't use sbrk(2) to get memory, but rather mmap(2), since this allows pages to be returned to the kernel's freepool again. This means less memory consumed, less paging and so on. (this also means that you cannot call sbrk/brk too destructively, and that's why I emulate them.) Please send me all your results, so I can see if this holds water. I need to make sure that all the comments make sense, so don't be surprised if they say something weird. If people generally don't complain, then I think it is a suitable replacement for the current malloc in freebsd, which it runs circles around. Poul-Henning /* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp * ---------------------------------------------------------------------------- * * $Id$ * */ #if defined(__i386__) && defined(__FreeBSD__) # warning FreeBSD i386 constants hardcoded. /* If these weren't defined here, they would be calculated on the fly */ # define malloc_pagesize 4096U # define malloc_pageshift 12U # define malloc_minsize 16U #endif /* __i386__ && __FreeBSD__ */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <memory.h> #include <err.h> #include <sys/types.h> #include <sys/mman.h> #ifdef MAIN # define SANITY # define DEBUG # define __inline /* */ #endif /* MAIN */ /* * This structure describes a page's worth of chunks. */ struct pginfo { struct pginfo *next; /* next on the free list */ void *page; /* Pointer to the page */ u_short size; /* size of this page's chunks */ u_short shift; /* How far to shift for this size chunks */ u_short free; /* How many free chunks */ u_short total; /* How many chunk */ u_long bits[1]; /* Which chunks are free */ }; /* * How many bits per u_long in the bitmap. * Change only if not 8 bits/byte */ #define MALLOC_BITS (8*sizeof(u_long)) /* * Magic values to put in the page_directory */ #define MALLOC_NOT_MINE ((struct pginfo*) 0) #define MALLOC_FREE ((struct pginfo*) 1) #define MALLOC_FIRST ((struct pginfo*) 2) #define MALLOC_FOLLOW ((struct pginfo*) 3) #define MALLOC_MAGIC ((struct pginfo*) 4) /* * The i386 architecture has some very convenient instructions. * We might as well use them. */ #ifdef __i386__ # warning i386 inline assembly used. #define ffs _ffs static __inline int _ffs(unsigned input) { int result; asm("bsfl %1,%0" : "=r" (result) : "r" (input)); return result+1; } #define fls _fls static __inline int _fls(unsigned input) { int result; asm("bsrl %1,%0" : "=r" (result) : "r" (input)); return result+1; } #define set_bit _set_bit static __inline void _set_bit(struct pginfo *pi, int bit) { asm("btsl %0,(%1)" : : "r" (bit & (MALLOC_BITS-1)), "r" (pi->bits+(bit/MALLOC_BITS))); } #define clr_bit _clr_bit static __inline void _clr_bit(struct pginfo *pi, int bit) { asm("btcl %0,(%1)" : : "r" (bit & (MALLOC_BITS-1)), "r" (pi->bits+(bit/MALLOC_BITS))); } #endif __i386__ /* * Set to one when malloc_init has been called */ static unsigned initialized; /* * The size of a page. * Must be a integral multiplum of the granularity of mmap(2). * Your toes will curl if it isn't a power of two */ #ifdef malloc_pagesize #define malloc_pagemask ((malloc_pagesize)-1) #define malloc_maxsize ((malloc_pagesize)>>1) #else static unsigned malloc_pagesize; #endif /* malloc_pagesize */ /* * Bitmask to find offset in page */ #ifndef malloc_pagemask static unsigned malloc_pagemask; #endif /* malloc_pagemask */ /* * malloc_pagesize == 1 << malloc_pageshift */ #ifndef malloc_pageshift static unsigned malloc_pageshift; #endif /* malloc_pageshift */ /* * The smallest allocation we bother about. * See comments in malloc_init() for details. * Must be power of two */ #ifndef malloc_minsize static unsigned malloc_minsize; #endif /* malloc_minsize */ /* * The largest chunk we care about. * Must be smaller than pagesize * Must be power of two */ #ifndef malloc_maxsize static unsigned malloc_maxsize; #endif /* malloc_maxsize */ /* * The offset from pagenumber to index into the page directory */ static u_long malloc_origo; /* * The last page we care about. * This is also the "brk()/sbrk()" point */ static u_long malloc_lastpage; /* * Pointer to page directory. * Allocated "as if with" malloc */ static struct pginfo **page_dir; /* * How many slots in the page directory */ static unsigned malloc_ninfo; /* * A small cache of pages so we don't call mmap/munmap too much */ #ifdef NFP #define NFP 10 static void *freepage[NFP]; #endif /* NFP */ static set_pgdir(void *, struct pginfo *); /* * Write something to stderr without all the luggage of stdio */ static void wrterror(char *p) { char *q = "malloc(): "; write(2,q,strlen(q)); write(2,p,strlen(p)); abort(); } #ifdef DEBUG void malloc_dump(FILE *fd) { struct pginfo **pd; int i,j; pd = page_dir; /* find last touched page */ for(i=malloc_ninfo-1;i>=0;i--) if (pd[i]) break; /* print out all the pages */ for(j=0;j<=i;j++) { fprintf(fd,"%08lx %5d ",(j+malloc_origo) << malloc_pageshift,j); if (pd[j] == MALLOC_NOT_MINE) { for(j++;j<=i && pd[j] == MALLOC_NOT_MINE;j++) ; j--; fprintf(fd,".. %5d not mine\n", j); } else if (pd[j] == MALLOC_FREE) { for(j++;j<=i && pd[j] == MALLOC_FREE;j++) ; j--; fprintf(fd,".. %5d free\n", j); } else if (pd[j] == MALLOC_FIRST) { for(j++;j<=i && pd[j] == MALLOC_FOLLOW;j++) ; j--; fprintf(fd,".. %5d in use\n", j); } else if (pd[j] < MALLOC_MAGIC) { fprintf(fd,"(%d)\n", j); } else { fprintf(fd,"%p %ld x %ld @ %p\n", pd[j],pd[j]->free, pd[j]->size, pd[j]->page); } } /* print out various info */ fprintf(fd,"Minsize\t%d\n",malloc_minsize); fprintf(fd,"Maxsize\t%d\n",malloc_maxsize); fprintf(fd,"Pagesize\t%d\n",malloc_pagesize); fprintf(fd,"Pageshift\t%d\n",malloc_pageshift); fprintf(fd,"FirstPage\t%ld\n",malloc_origo); fprintf(fd,"LastPage\t%ld %lx\n",malloc_lastpage,malloc_lastpage << malloc_pageshift); fprintf(fd,"Break\t%ld\n",(u_long)sbrk(0) >> malloc_pageshift); } #endif /* DEBUG */ /* * Allocate a number of pages from the OS */ static __inline caddr_t map_pages(void *where, int pages) { caddr_t result; if (!where) { where = (void*)(malloc_lastpage << malloc_pageshift); malloc_lastpage += pages; } result = mmap(where, pages << malloc_pageshift, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON, -1, 0); if (result != (caddr_t) -1) return result; return 0; } /* * Return pages to the OS if we can */ static __inline void unmap_pages(caddr_t ptr, int pages) { if (munmap(ptr, pages << malloc_pageshift)) wrterror("botch: munmap failed. (wild pointer ?)\n"); } /* * Set a bit in the bitmap */ #ifndef set_bit static __inline void set_bit(struct pginfo *pi, int bit) { pi->bits[bit/MALLOC_BITS] |= 1<<(bit%MALLOC_BITS); } #endif /* set_bit */ /* * Clear a bit in the bitmap */ #ifndef clr_bit static __inline void clr_bit(struct pginfo *pi, int bit) { pi->bits[bit/MALLOC_BITS] &= ~(1<<(bit%MALLOC_BITS)); } #endif /* clr_bit */ #ifdef SANITY #ifndef tst_bit /* * Test a bit in the bitmap */ static __inline int tst_bit(struct pginfo *pi, int bit) { return pi->bits[bit/MALLOC_BITS] & (1<<(bit%MALLOC_BITS)); } #endif /* tst_bit */ #endif /* SANITY */ /* * Find last bit */ #ifndef fls static __inline int fls(int size) { int i = 1; while (size >>= 1) i++; return i; } #endif /* fls */ /* * Extend page directory */ static int extend_page_directory(u_long index) { struct pginfo **new,**old; int i; /* Make it this many pages */ i = index * sizeof *page_dir; i /= malloc_pagesize; i += 2; /* Get new pages, if you used this much mem you don't care :-) */ new = (struct pginfo**) map_pages(0,i); if (!new) return 0; /* Copy the old stuff */ memset(new, 0, i * malloc_pagesize); memcpy(new, page_dir, malloc_ninfo * sizeof *page_dir); /* register the new size */ malloc_ninfo = i * malloc_pagesize / sizeof *page_dir; /* swap the pointers */ old = page_dir; page_dir = new; /* Mark the pages */ set_pgdir(new,MALLOC_FIRST); while (--i) { new += malloc_pagesize; set_pgdir(new,MALLOC_FOLLOW); } /* Now free the old stuff */ free(old); } /* * Set entry in page directory. * Extend page directory if need be. */ static __inline int set_pgdir(void *ptr, struct pginfo *info) { u_long index = ((u_long)ptr >> malloc_pageshift) - malloc_origo; if (index >= malloc_ninfo) extend_page_directory(index); page_dir[index] = info; return 1; } /* * Initialize the world */ static void malloc_init () { int i; extern char end; #ifndef malloc_pagesize /* determine our pagesize */ malloc_pagesize = getpagesize(); #endif /* malloc_pagesize */ #ifndef malloc_pageshift /* determine how much we shift by to get there */ for (i = malloc_pagesize; i > 1; i >>= 1) malloc_pageshift++; #endif /* malloc_pageshift */ #ifndef malloc_pagemask /* and how many bits are left */ malloc_pagemask = malloc_pagesize - 1; #endif /* malloc_pagemask */ #ifndef malloc_maxsize /* we will allocate whole pages if bigger than: */ malloc_maxsize = malloc_pagesize >> 1; #endif /* malloc_maxsize */ #ifndef malloc_minsize /* * find the smallest size allocation we will bother about * this is determined as the smallest allocation that can hold * it's own pginfo; */ i = 2; for(;;) { int j; /* Figure out the size of the bits */ j = malloc_pagesize/i; j /= 8; if (j < sizeof(u_long)) j = sizeof (u_long); if (sizeof(struct pginfo) + j - sizeof (u_long) <= i) break; i += i; } malloc_minsize = i; #endif /* malloc_minsize */ malloc_lastpage = ((u_long)&end + malloc_pagesize-1) >> malloc_pageshift; /* Allocate one page for the page directory */ page_dir = (struct pginfo **) map_pages(0,1); if (!page_dir) wrterror("fatal: my first mmap failed. (check limits ?)\n"); /* * We need a maximum of malloc_pageshift buckets, steal these from the * front of the page_directory; */ malloc_origo = (u_long) page_dir >> malloc_pageshift; malloc_origo -= malloc_pageshift; /* Clear it */ memset(page_dir,0,malloc_pagesize); /* Find out how much it tells us */ malloc_ninfo = malloc_pagesize / sizeof *page_dir; /* Plug the page directory into itself */ i = set_pgdir(page_dir,MALLOC_FIRST); if (!i) wrterror("fatal: couldn't set myself in the page directory\n"); /* Been here, done that */ initialized++; } /* * Allocate a number of complete pages */ void * malloc_pages(size_t size) { void *p,*q; int i; struct pginfo **pi,**pe; /* How many pages ? */ size += (malloc_pagesize-1); size >>= malloc_pageshift; /* Look for free pages before asking for more */ pi = page_dir + malloc_pageshift; pe = page_dir + (malloc_lastpage - malloc_origo); p = 0; if (size == 1) { #ifdef NFP for(i=0;i<NFP;i++) if (freepage[i]) { p = freepage[i]; freepage[i] = 0; goto found; } #endif /* NFP */ /* Optimise this case since it's so common */ for (; pi < pe; pi++) { if (*pi == MALLOC_FREE) { /* Found a page, now find the pointer to it */ p = (void*)((pi - page_dir + malloc_origo) << malloc_pageshift); break; } } } else { for (; pi < pe; pi++) { if (pi[0] != MALLOC_FREE) continue; if (pi[size-1] != MALLOC_FREE) { /* If the last one isn't OK, just move on, nothing in * between will be any good then */ pi += (size-1); continue; } for(i=1;i<size;i++) if (pi[i] != MALLOC_FREE) { pi += i; goto miss; } /* Found the pages we need, now find the pointer to them */ p = (void*)((pi - page_dir + malloc_origo) << malloc_pageshift); break; miss: } } /* Map the pages again */ p = map_pages(p,size); /* ENOLUCK */ if (!p) return 0; #ifdef NFP found: #endif /* NFP */ /* Mark the pages in the directory */ i = set_pgdir(p,MALLOC_FIRST); if (!i) return 0; for (q=p; --size; ) { q += malloc_pagesize; i = set_pgdir(q,MALLOC_FOLLOW); if (!i) return 0; } return p; } /* * Allocate a page of fragments */ static int malloc_make_chunks(int bits) { struct pginfo *bp; void *pp; int i,k,l; /* Allocate a new bucket */ pp = malloc_pages(malloc_pagesize); if (!pp) return 0; l = sizeof *bp - sizeof(u_long); l += sizeof(u_long) * (((malloc_pagesize >> bits)+MALLOC_BITS-1) / MALLOC_BITS); if ((1<<(bits-1)) <= l) { bp = (struct pginfo *)pp; l += (1<<bits); l--; l >>= bits; } else { bp = (struct pginfo *)malloc(l); l = 0; } if (!bp) return 0; i = set_pgdir(pp,bp); if (!i) return 0; bp->size = (1<<bits); bp->shift = bits; bp->total = bp->free = malloc_pagesize >> bits; bp->next = 0; bp->page = pp; #ifdef SANITY if (bp->size < malloc_minsize || bp->size > malloc_maxsize) wrterror("sanity: chunk has abnormal size.\n"); #endif /* SANITY */ /* We can safely assume that there is nobody in this chain */ page_dir[bits] = bp; /* set all valid bits in the bits */ k = bp->total; i = 0; for(;k-i >= MALLOC_BITS; i += MALLOC_BITS) bp->bits[i / MALLOC_BITS] = ~0; for(; i < k; i++) set_bit(bp,i); /* We may have used the first ones already */ for(i=0;i<l;i++) { clr_bit(bp,i); bp->free--; bp->total--; } return 1; } /* * Allocate a fragment */ static void __inline * malloc_bytes(size_t size) { int j; /* Find the right bucket */ if (size < malloc_minsize) size = malloc_minsize; j = fls((size)-1); #ifdef EXTRA_SANITY if (size > (1<<j)) wrterror("extra sanity: choose to small chunk\n"); else if (size < (1<<(j-1))) wrterror("extra sanity: choose to big chunk\n"); #endif while(1) { struct pginfo *bp; bp = page_dir[j]; if (bp) { register int k; register u_long *lp = bp->bits; for (; !*lp; lp++) ; k = ffs(*lp) - 1; *lp ^= 1<<k; bp->free--; if (!bp->free) { page_dir[j] = bp->next; bp->next = 0; } k += (lp-bp->bits)*MALLOC_BITS; return bp->page + (k << bp->shift); } if (!malloc_make_chunks(j)) return 0; } } void * malloc(size_t size) { if (!initialized) malloc_init(); if (size <= malloc_maxsize) return malloc_bytes(size); return malloc_pages(size); } void * realloc(void *ptr, size_t size) { void *p; u_long osize; struct pginfo **mp; if (!initialized) malloc_init(); if (ptr && !size) { free(ptr); return 0; } mp = &page_dir[((u_long)ptr >> malloc_pageshift) - malloc_origo]; if (*mp == MALLOC_FIRST) { osize = malloc_pagesize; while (mp[1] == MALLOC_FOLLOW) { osize += malloc_pagesize; mp++; } } else { osize = (*mp)->size; } p = malloc(size); if (p && ptr) { if (osize < size) memcpy(p,ptr,osize); else memcpy(p,ptr,size); free(ptr); } return p; } static void free_pages(void *ptr) { u_long page; int i,j,index; #ifdef SANITY if ((u_long)ptr & malloc_pagemask) wrterror("sanity: freeing messed up page pointer.\n"); #endif /* SANITY */ page = (u_long)ptr >> malloc_pageshift; index = page - malloc_origo; /* Count how many pages it is */ for (i = 1; page_dir[index+i] == MALLOC_FOLLOW; i++) ; #ifdef NFP for(j=0;j<NFP;j++) if (!freepage[j]) { freepage[j] = ptr; page_dir[index++] = MALLOC_FREE; if (!--i) return; ptr += malloc_pagesize; } #endif /* NFP */ /* Hand 'em back to the good 'ol OS */ unmap_pages(ptr,i); /* Now update the directory */ while(i--) page_dir[index++] = MALLOC_FREE; } void free(void *ptr) { u_long page; struct pginfo *info; int i,index; struct pginfo **mp; if (!initialized) malloc_init(); page = (u_long)ptr >> malloc_pageshift; index = page - malloc_origo; info = page_dir[index]; if (info == MALLOC_FIRST) return free_pages(ptr); #ifdef SANITY if (info <= MALLOC_MAGIC) wrterror("sanity: freeing something wrong.\n"); if ((u_long)ptr & (info->size - 1)) wrterror("sanity: freeing messed up chunk pointer\n"); #endif /* SANITY */ i = ((u_long)ptr & malloc_pagemask) >> info->shift; #ifdef SANITY if (tst_bit(info,i)) wrterror("sanity: bit already set.\n"); #endif /* SANITY */ set_bit(info,i); info->free++; mp = page_dir + info->shift; if (info->free == 1) { /* Link in front of chain */ info->next = *mp; *mp = info; return; } if (info->free != info->total) return; /* * This keeps at least one index-page around for each size. * The benefit is decent considering the overhead (7 pages) */ if (!info->next && (page_dir[info->shift] == info)) return; while (*mp != info) mp = &((*mp)->next); *mp = info->next; set_pgdir(info->page,MALLOC_FIRST); if((void*)info->page == (void*)info) { free_pages(info->page); } else { free_pages(info->page); free(info); } } char * brk(const char *ptr) { if (!initialized) malloc_init(); wrterror("botch: somebody called brk(). (Don't!)\n"); return 0; } #ifndef NOSBRK char * sbrk(int delta) { if (!initialized) malloc_init(); if (delta) wrterror("botch: somebody called sbrk(!=0). (Don't)\n"); return (void*) (malloc_lastpage << malloc_pageshift); } #endif /* NOSBRK */ #ifdef MAIN /* TEST STUFF */ #define NBUCKETS 2000 #define NOPS 1000000 void *foo[NBUCKETS]; int main() { int i,j,k; void *bar; setbuf(stdout,0); setbuf(stderr,0); bar = malloc(1); malloc_dump(stderr); free(bar); malloc_dump(stderr); for (i = 0 ; i < NOPS ; i++) { j = rand() % NBUCKETS; if (foo[j]) { /* printf("%6d F [%4d] %8p\n",i,j,foo[j]); */ free(foo[j]); foo[j] = 0; } else { k = rand() % malloc_maxsize; foo[j] = malloc(k); if (!foo[j]) printf("%6d M [%4d] %8p %d\n",i,j,foo[j],k); /* printf("%6d M [%4d] %8p %d\n",i,j,foo[j],k); */ } } for (j = 0 ; j < NBUCKETS ; j++) { if (foo[j]) { /* printf("%6d F [%4d] %8p\n",i,j+malloc_origo,foo[j]); */ free(foo[j]); foo[j] = 0; } } malloc_dump(stderr); return 0; } #endif /* MAIN */ -- Poul-Henning Kamp | phk@FreeBSD.ORG FreeBSD Core-team. http://www.freebsd.org/~phk | phk@login.dknet.dk Private mailbox. whois: [PHK] | phk@ref.tfs.com TRW Financial Systems, Inc. Just that: dried leaves in boiling water ?
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199507222206.PAA06101>