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>
index | next in thread | raw e-mail
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 ?
help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199507222206.PAA06101>
