Commit be2df608 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Be more thrifty with memory: allocate bufferpool entries to multiples

of PAGE_SIZE.

This reduces memory overhead in programmings creating many FUSE
filesystems, such as termite.
parent c32f2a18
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"log" "log"
"sync" "sync"
"strings"
"unsafe" "unsafe"
) )
...@@ -12,6 +13,7 @@ var _ = log.Println ...@@ -12,6 +13,7 @@ var _ = log.Println
type BufferPool interface { type BufferPool interface {
AllocBuffer(size uint32) []byte AllocBuffer(size uint32) []byte
FreeBuffer(slice []byte) FreeBuffer(slice []byte)
String() string
} }
type GcBufferPool struct { type GcBufferPool struct {
...@@ -31,74 +33,62 @@ func (me *GcBufferPool) FreeBuffer(slice []byte) { ...@@ -31,74 +33,62 @@ func (me *GcBufferPool) FreeBuffer(slice []byte) {
} }
// BufferPool implements a pool of buffers that returns slices with // BufferPool implements a pool of buffers that returns slices with
// capacity (2^e * PAGESIZE) for e=0,1,... which have possibly been // capacity of a multiple of PAGESIZE, which have possibly been used,
// used, and may contain random contents. // and may contain random contents.
type BufferPoolImpl struct { type BufferPoolImpl struct {
lock sync.Mutex lock sync.Mutex
// For each exponent a list of slice pointers. // For each page size multiple a list of slice pointers.
buffersByExponent [][][]byte buffersBySize [][][]byte
// start of slice -> exponent. // start of slice => true
outstandingBuffers map[uintptr]uint outstandingBuffers map[uintptr]bool
// Total count of created buffers. Handy for finding memory // Total count of created buffers. Handy for finding memory
// leaks. // leaks.
createdBuffers int createdBuffers int
} }
// IntToExponent the smallest E such that 2^E >= Z.
func IntToExponent(z int) uint {
x := z
var exp uint = 0
for x > 1 {
exp++
x >>= 1
}
if z > (1 << exp) {
exp++
}
return exp
}
func NewBufferPool() *BufferPoolImpl { func NewBufferPool() *BufferPoolImpl {
bp := new(BufferPoolImpl) bp := new(BufferPoolImpl)
bp.buffersByExponent = make([][][]byte, 0, 8) bp.buffersBySize = make([][][]byte, 0, 32)
bp.outstandingBuffers = make(map[uintptr]uint) bp.outstandingBuffers = make(map[uintptr]bool)
return bp return bp
} }
func (me *BufferPoolImpl) String() string { func (me *BufferPoolImpl) String() string {
me.lock.Lock() me.lock.Lock()
defer me.lock.Unlock() defer me.lock.Unlock()
s := fmt.Sprintf("created: %v\noutstanding %v\n",
me.createdBuffers, len(me.outstandingBuffers)) result := []string{}
for exp, bufs := range me.buffersByExponent { for exp, bufs := range me.buffersBySize {
s = s + fmt.Sprintf("%d = %d\n", exp, len(bufs)) if len(bufs) > 0 {
result = append(result, fmt.Sprintf("%d=%d\n", exp, len(bufs)))
}
} }
return s return fmt.Sprintf("created: %v\noutstanding %v\n%s",
me.createdBuffers, len(me.outstandingBuffers),
strings.Join(result, ", "))
} }
func (me *BufferPoolImpl) getBuffer(exponent uint) []byte { func (me *BufferPoolImpl) getBuffer(pageCount int) []byte {
if len(me.buffersByExponent) <= int(exponent) { for ; pageCount < len(me.buffersBySize); pageCount++ {
return nil bufferList := me.buffersBySize[pageCount]
if len(bufferList) > 0 {
result := bufferList[len(bufferList)-1]
me.buffersBySize[pageCount] = me.buffersBySize[pageCount][:len(bufferList)-1]
return result
}
} }
bufferList := me.buffersByExponent[exponent]
if len(bufferList) == 0 { return nil
return nil
}
result := bufferList[len(bufferList)-1]
me.buffersByExponent[exponent] = me.buffersByExponent[exponent][:len(bufferList)-1]
return result
} }
func (me *BufferPoolImpl) addBuffer(slice []byte, exp uint) { func (me *BufferPoolImpl) addBuffer(slice []byte, pages int) {
for len(me.buffersByExponent) <= int(exp) { for len(me.buffersBySize) <= int(pages) {
me.buffersByExponent = append(me.buffersByExponent, make([][]byte, 0)) me.buffersBySize = append(me.buffersBySize, make([][]byte, 0))
} }
me.buffersByExponent[exp] = append(me.buffersByExponent[exp], slice) me.buffersBySize[pages] = append(me.buffersBySize[pages], slice)
} }
// AllocBuffer creates a buffer of at least the given size. After use, // AllocBuffer creates a buffer of at least the given size. After use,
...@@ -108,28 +98,28 @@ func (me *BufferPoolImpl) AllocBuffer(size uint32) []byte { ...@@ -108,28 +98,28 @@ func (me *BufferPoolImpl) AllocBuffer(size uint32) []byte {
if sz < PAGESIZE { if sz < PAGESIZE {
sz = PAGESIZE sz = PAGESIZE
} }
exp := IntToExponent(sz) if sz % PAGESIZE != 0 {
rounded := 1 << exp sz += PAGESIZE
}
exp -= IntToExponent(PAGESIZE) psz := sz / PAGESIZE
me.lock.Lock() me.lock.Lock()
defer me.lock.Unlock() defer me.lock.Unlock()
b := me.getBuffer(exp) var b []byte
b = me.getBuffer(psz)
if b == nil { if b == nil {
me.createdBuffers++ me.createdBuffers++
b = make([]byte, size, rounded) b = make([]byte, size, psz * PAGESIZE)
} else { } else {
b = b[:size] b = b[:size]
} }
me.outstandingBuffers[uintptr(unsafe.Pointer(&b[0]))] = exp me.outstandingBuffers[uintptr(unsafe.Pointer(&b[0]))] = true
// FUSE throttles to ~10 outstanding requests, no normally, // For testing should not have more than 20 buffers outstanding.
// should not have more than 20 buffers outstanding.
if paranoia && (me.createdBuffers > 50 || len(me.outstandingBuffers) > 50) { if paranoia && (me.createdBuffers > 50 || len(me.outstandingBuffers) > 50) {
panic("Leaking buffers") panic("Leaking buffers")
} }
...@@ -144,23 +134,18 @@ func (me *BufferPoolImpl) FreeBuffer(slice []byte) { ...@@ -144,23 +134,18 @@ func (me *BufferPoolImpl) FreeBuffer(slice []byte) {
if slice == nil { if slice == nil {
return return
} }
sz := cap(slice) if cap(slice) % PAGESIZE != 0 {
if sz < PAGESIZE {
return
}
exp := IntToExponent(sz)
rounded := 1 << exp
if rounded != sz {
return return
} }
slice = slice[:sz] psz := cap(slice) / PAGESIZE
slice = slice[:psz]
key := uintptr(unsafe.Pointer(&slice[0])) key := uintptr(unsafe.Pointer(&slice[0]))
me.lock.Lock() me.lock.Lock()
defer me.lock.Unlock() defer me.lock.Unlock()
exp, ok := me.outstandingBuffers[key] ok := me.outstandingBuffers[key]
if ok { if ok {
me.addBuffer(slice, exp) me.addBuffer(slice, psz)
delete(me.outstandingBuffers, key) delete(me.outstandingBuffers, key)
} }
} }
...@@ -7,25 +7,6 @@ import ( ...@@ -7,25 +7,6 @@ import (
var _ = fmt.Println var _ = fmt.Println
func TestIntToExponent(t *testing.T) {
e := IntToExponent(1)
if e != 0 {
t.Error("1", e)
}
e = IntToExponent(2)
if e != 1 {
t.Error("2", e)
}
e = IntToExponent(3)
if e != 2 {
t.Error("3", e)
}
e = IntToExponent(4)
if e != 2 {
t.Error("4", e)
}
}
func TestBufferPool(t *testing.T) { func TestBufferPool(t *testing.T) {
bp := NewBufferPool() bp := NewBufferPool()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment