-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change-Id: Ie0bd1c8060dd22f1c343f11b0bbc84fba566353a
- Loading branch information
Showing
9 changed files
with
314 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package bbp | ||
|
||
import ( | ||
"container/list" | ||
"sync" | ||
"syscall" | ||
|
||
"github.com/jxskiss/gopkg/v2/internal" | ||
"github.com/jxskiss/gopkg/v2/internal/linkname" | ||
) | ||
|
||
var sysPageSize = syscall.Getpagesize() | ||
|
||
func alignChunkSize(chunkSize int) int { | ||
if chunkSize < sysPageSize { | ||
chunkSize = sysPageSize | ||
} | ||
return int(internal.NextPowerOfTwo(uint(chunkSize))) | ||
} | ||
|
||
var arenaPool = sync.Pool{ | ||
New: func() any { return &Arena{} }, | ||
} | ||
|
||
// Arena allocates memory in chunk mode, and serves requests to allocate | ||
// small byte slices, after working with the memory chunks, | ||
// user should call Free to release the allocated memory together. | ||
// It's efficient for memory allocation-heavy workloads. | ||
type Arena struct { | ||
chunkSize int | ||
allocFunc func(size int) []byte | ||
freeFunc func([]byte) | ||
lst list.List | ||
} | ||
|
||
// OffHeapArena is similar to Arena, except that it allocates memory | ||
// directly from operating system instead of Go's runtime. | ||
// | ||
// Note that after working with the memory chunks, user **MUST** call | ||
// Free to return the memory to operating system, else memory leaks. | ||
type OffHeapArena Arena | ||
|
||
// NewArena creates an Arena object, it allocates memory from the sized | ||
// buffer pools. | ||
// The method Free returns memory chunks to the pool for reusing, | ||
// after which both the arena and the byte slices allocated from the arena | ||
// **MUST NOT** be touched again. | ||
// chunkSize will be round up to the next power of two that is | ||
// greater than or equal to the system's PAGE_SIZE. | ||
func NewArena(chunkSize int) *Arena { | ||
chunkSize = alignChunkSize(chunkSize) | ||
poolIdx := indexGet(chunkSize) | ||
bp := sizedPools[poolIdx] | ||
a := arenaPool.Get().(*Arena) | ||
a.chunkSize = chunkSize | ||
a.allocFunc = bp.Get | ||
a.freeFunc = bp.Put | ||
return a | ||
} | ||
|
||
// NewOffHeapArena creates an OffHeapArena which allocates memory directly | ||
// from operating system (without cgo). | ||
// The method Free frees allocated memory chunks. | ||
// Free must be called after working with the arena to avoid memory leaks. | ||
// After Free being called, both the arena and the byte slices allocated | ||
// from the arena **MUST NOT** be touched again. | ||
// chunkSize will be round up to the next power of two that is | ||
// greater than or equal to the system's PAGE_SIZE. | ||
func NewOffHeapArena(chunkSize int) *OffHeapArena { | ||
chunkSize = alignChunkSize(chunkSize) | ||
a := arenaPool.Get().(*Arena) | ||
a.chunkSize = chunkSize | ||
a.allocFunc = offHeapAlloc | ||
a.freeFunc = offHeapFree | ||
return (*OffHeapArena)(a) | ||
} | ||
|
||
func offHeapAlloc(chunkSize int) []byte { | ||
return linkname.Runtime_sysAlloc(uintptr(chunkSize)) | ||
} | ||
|
||
func offHeapFree(buf []byte) { | ||
linkname.Runtime_sysFree(buf) | ||
} | ||
|
||
// Alloc allocates small byte slice from the arena. | ||
func (a *Arena) Alloc(length, capacity int) []byte { | ||
if capacity > a.chunkSize>>2 { | ||
return make([]byte, length, capacity) | ||
} | ||
|
||
if active := a.lst.Back(); active != nil { | ||
chunk := active.Value.(*memChunk) | ||
if buf, ok := chunk.alloc(length, capacity); ok { | ||
return buf | ||
} | ||
} | ||
|
||
chunk := a.allocNewChunk() | ||
buf, _ := chunk.alloc(length, capacity) | ||
return buf | ||
} | ||
|
||
// Free releases all memory chunks managed by the arena. | ||
// It returns the memory chunks to pool for reusing. | ||
func (a *Arena) Free() { | ||
for node := a.lst.Front(); node != nil; node = node.Next() { | ||
chunk := node.Value.(*memChunk) | ||
a.freeFunc(chunk.buf) | ||
} | ||
a.lst.Init() // clear the list | ||
arenaPool.Put(a) | ||
} | ||
|
||
func (a *Arena) allocNewChunk() *memChunk { | ||
buf := a.allocFunc(a.chunkSize) | ||
chunk := &memChunk{buf: buf} | ||
a.lst.PushBack(chunk) | ||
return chunk | ||
} | ||
|
||
type memChunk struct { | ||
buf []byte | ||
i int | ||
} | ||
|
||
func (c *memChunk) alloc(length, capacity int) ([]byte, bool) { | ||
j := c.i + capacity | ||
if j < cap(c.buf) { | ||
c.i = j | ||
buf := c.buf[j-capacity : j] | ||
return buf[0:length:capacity], true | ||
} | ||
return nil, false | ||
} | ||
|
||
// Alloc allocates small byte slice from the arena. | ||
func (a *OffHeapArena) Alloc(length, capacity int) []byte { | ||
return (*Arena)(a).Alloc(length, capacity) | ||
} | ||
|
||
// Free returns all memory chunks managed by the arena to the operating system. | ||
func (a *OffHeapArena) Free() { | ||
(*Arena)(a).Free() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
//go:build cgo | ||
|
||
package bbp | ||
|
||
/* | ||
#include <stdlib.h> | ||
*/ | ||
import "C" | ||
|
||
import ( | ||
"unsafe" | ||
|
||
"github.com/jxskiss/gopkg/v2/internal/unsafeheader" | ||
) | ||
|
||
// NewCgoArena creates an OffHeapArena which allocates memory by calling | ||
// cgo `C.malloc`. cgo must be enabled to use this. | ||
// The method Free frees allocated memory chunks. | ||
// Free must be called after working with the arena to avoid memory leaks. | ||
// After Free being called, both the arena and the byte slices allocated | ||
// from the arena **MUST NOT** be touched again. | ||
// chunkSize will be round up to the next power of two that is | ||
// greater than or equal to the system's PAGE_SIZE. | ||
func NewCgoArena(chunkSize int) *OffHeapArena { | ||
chunkSize = alignChunkSize(chunkSize) | ||
a := arenaPool.Get().(*Arena) | ||
a.chunkSize = chunkSize | ||
a.allocFunc = cgoAlloc | ||
a.freeFunc = cgoFree | ||
return (*OffHeapArena)(a) | ||
} | ||
|
||
func cgoAlloc(size int) []byte { | ||
ptr := C.malloc(C.size_t(size)) | ||
if ptr == nil { | ||
// Don't allow the caller to capture this panic, | ||
// and block to wait the program exiting. | ||
go func() { | ||
panic("bbp.Arena: out of memory") | ||
}() | ||
select {} | ||
} | ||
buf := *(*[]byte)(unsafe.Pointer(&unsafeheader.Slice{ | ||
Data: ptr, | ||
Len: size, | ||
Cap: size, | ||
})) | ||
return buf | ||
} | ||
|
||
func cgoFree(buf []byte) { | ||
ptr := (*unsafeheader.Slice)(unsafe.Pointer(&buf)).Data | ||
C.free(ptr) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package bbp | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestArena(t *testing.T) { | ||
type arenaIface interface { | ||
Alloc(length, capacity int) []byte | ||
Free() | ||
} | ||
|
||
makeArenas := func() []arenaIface { | ||
return []arenaIface{ | ||
NewArena(456), | ||
NewOffHeapArena(567), | ||
NewCgoArena(789), | ||
} | ||
} | ||
|
||
getChunkSize := func(a arenaIface) int { | ||
switch a := a.(type) { | ||
case *Arena: | ||
return a.chunkSize | ||
case *OffHeapArena: | ||
return a.chunkSize | ||
} | ||
panic("unreachable") | ||
} | ||
|
||
t.Logf("sysPageSize= %v", sysPageSize) | ||
for _, a := range makeArenas() { | ||
assert.Equal(t, sysPageSize, getChunkSize(a)) | ||
|
||
n := sysPageSize / 2 | ||
buf := a.Alloc(10, n) | ||
assert.Equal(t, 10, len(buf)) | ||
assert.Equal(t, n, cap(buf)) | ||
|
||
for { | ||
if n > 2*sysPageSize+1 { | ||
break | ||
} | ||
buf := a.Alloc(10, 100) | ||
n += cap(buf) | ||
assert.Equal(t, 10, len(buf)) | ||
assert.Equal(t, 100, cap(buf)) | ||
} | ||
a.Free() | ||
} | ||
} | ||
|
||
func BenchmarkNewArena(b *testing.B) { | ||
b.ReportAllocs() | ||
for i := 0; i < b.N; i++ { | ||
a := NewArena(sysPageSize) | ||
a.Free() | ||
} | ||
} | ||
|
||
func BenchmarkNewOffHeapArena(b *testing.B) { | ||
b.ReportAllocs() | ||
for i := 0; i < b.N; i++ { | ||
a := NewOffHeapArena(sysPageSize) | ||
a.Free() | ||
} | ||
} | ||
|
||
func BenchmarkNewCgoArena(b *testing.B) { | ||
b.ReportAllocs() | ||
for i := 0; i < b.N; i++ { | ||
a := NewCgoArena(sysPageSize) | ||
a.Free() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters