-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from huandu/feature-arena
support arena and add Allocator api
- Loading branch information
Showing
22 changed files
with
1,348 additions
and
739 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,3 +34,7 @@ debug_test | |
|
||
# Mac | ||
.DS_Store | ||
|
||
# go workspace | ||
go.work | ||
go.work.sum |
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,154 @@ | ||
// Copyright 2023 Huan Du. All rights reserved. | ||
// Licensed under the MIT license that can be found in the LICENSE file. | ||
|
||
package clone | ||
|
||
import ( | ||
"reflect" | ||
"runtime" | ||
"unsafe" | ||
) | ||
|
||
var typeOfAllocator = reflect.TypeOf(Allocator{}) | ||
|
||
// Allocator is a utility type for memory allocation. | ||
type Allocator struct { | ||
pool unsafe.Pointer | ||
new func(pool unsafe.Pointer, t reflect.Type) reflect.Value | ||
makeSlice func(pool unsafe.Pointer, t reflect.Type, len, cap int) reflect.Value | ||
makeMap func(pool unsafe.Pointer, t reflect.Type, n int) reflect.Value | ||
makeChan func(pool unsafe.Pointer, t reflect.Type, buffer int) reflect.Value | ||
} | ||
|
||
// AllocatorMethods defines all methods required by allocator. | ||
// If any of these methods is nil, allocator will use default method which allocates memory from heap. | ||
type AllocatorMethods struct { | ||
New func(pool unsafe.Pointer, t reflect.Type) reflect.Value | ||
MakeSlice func(pool unsafe.Pointer, t reflect.Type, len, cap int) reflect.Value | ||
MakeMap func(pool unsafe.Pointer, t reflect.Type, n int) reflect.Value | ||
MakeChan func(pool unsafe.Pointer, t reflect.Type, buffer int) reflect.Value | ||
} | ||
|
||
// FromHeap returns an allocator which allocate memory from heap. | ||
func FromHeap() *Allocator { | ||
return heapAllocator | ||
} | ||
|
||
// NewAllocator creates an allocator which allocate memory from the pool. | ||
// | ||
// If methods.New is not nil, the allocator itself is created by calling methods.New. | ||
func NewAllocator(pool unsafe.Pointer, methods *AllocatorMethods) (allocator *Allocator) { | ||
if methods.New == nil { | ||
allocator = &Allocator{ | ||
pool: pool, | ||
new: methods.New, | ||
makeSlice: methods.MakeSlice, | ||
makeMap: methods.MakeMap, | ||
makeChan: methods.MakeChan, | ||
} | ||
} else { | ||
// Allocate the allocator from the pool. | ||
val := methods.New(pool, typeOfAllocator) | ||
allocator = (*Allocator)(unsafe.Pointer(val.Pointer())) | ||
runtime.KeepAlive(val) | ||
|
||
*allocator = Allocator{ | ||
pool: pool, | ||
new: methods.New, | ||
makeSlice: methods.MakeSlice, | ||
makeMap: methods.MakeMap, | ||
makeChan: methods.MakeChan, | ||
} | ||
} | ||
|
||
if allocator.new == nil { | ||
allocator.new = heapNew | ||
} | ||
|
||
if allocator.makeSlice == nil { | ||
allocator.makeSlice = heapMakeSlice | ||
} | ||
|
||
if allocator.makeMap == nil { | ||
allocator.makeMap = heapMakeMap | ||
} | ||
|
||
if allocator.makeChan == nil { | ||
allocator.makeChan = heapMakeChan | ||
} | ||
|
||
return allocator | ||
} | ||
|
||
// New returns a new zero value of t. | ||
func (a *Allocator) New(t reflect.Type) reflect.Value { | ||
return a.new(a.pool, t) | ||
} | ||
|
||
// MakeSlice creates a new zero-initialized slice value of t with len and cap. | ||
func (a *Allocator) MakeSlice(t reflect.Type, len, cap int) reflect.Value { | ||
return a.makeSlice(a.pool, t, len, cap) | ||
} | ||
|
||
// MakeMap creates a new map with minimum size n. | ||
func (a *Allocator) MakeMap(t reflect.Type, n int) reflect.Value { | ||
return a.makeMap(a.pool, t, n) | ||
} | ||
|
||
// MakeChan creates a new chan with buffer. | ||
func (a *Allocator) MakeChan(t reflect.Type, buffer int) reflect.Value { | ||
return a.makeChan(a.pool, t, buffer) | ||
} | ||
|
||
// Clone recursively deep clone val to a new value with memory allocated from a. | ||
func (a *Allocator) Clone(val reflect.Value) reflect.Value { | ||
if !val.IsValid() { | ||
return val | ||
} | ||
|
||
state := &cloneState{ | ||
allocator: a, | ||
} | ||
return state.clone(val) | ||
} | ||
|
||
// CloneSlowly recursively deep clone val to a new value with memory allocated from a. | ||
// It marks all cloned values internally, thus it can clone v with cycle pointer. | ||
func (a *Allocator) CloneSlowly(val reflect.Value) reflect.Value { | ||
if !val.IsValid() { | ||
return val | ||
} | ||
|
||
state := &cloneState{ | ||
allocator: a, | ||
visited: visitMap{}, | ||
invalid: invalidPointers{}, | ||
} | ||
cloned := state.clone(val) | ||
state.fix(cloned) | ||
return cloned | ||
} | ||
|
||
// The heapAllocator allocates memory from heap. | ||
var heapAllocator = &Allocator{ | ||
new: heapNew, | ||
makeSlice: heapMakeSlice, | ||
makeMap: heapMakeMap, | ||
makeChan: heapMakeChan, | ||
} | ||
|
||
func heapNew(pool unsafe.Pointer, t reflect.Type) reflect.Value { | ||
return reflect.New(t) | ||
} | ||
|
||
func heapMakeSlice(pool unsafe.Pointer, t reflect.Type, len, cap int) reflect.Value { | ||
return reflect.MakeSlice(t, len, cap) | ||
} | ||
|
||
func heapMakeMap(pool unsafe.Pointer, t reflect.Type, n int) reflect.Value { | ||
return reflect.MakeMapWithSize(t, n) | ||
} | ||
|
||
func heapMakeChan(pool unsafe.Pointer, t reflect.Type, buffer int) reflect.Value { | ||
return reflect.MakeChan(t, buffer) | ||
} |
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,64 @@ | ||
// Copyright 2023 Huan Du. All rights reserved. | ||
// Licensed under the MIT license that can be found in the LICENSE file. | ||
|
||
package clone | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"runtime" | ||
"sync" | ||
"unsafe" | ||
) | ||
|
||
func ExampleAllocator() { | ||
type Foo struct { | ||
Bar int | ||
} | ||
|
||
typeOfFoo := reflect.TypeOf(Foo{}) | ||
poolUsed := 0 // For test only. | ||
|
||
// A sync pool to allocate Foo. | ||
p := &sync.Pool{ | ||
New: func() interface{} { | ||
return &Foo{} | ||
}, | ||
} | ||
|
||
// Creates a custom allocator using p as pool. | ||
allocator := NewAllocator(unsafe.Pointer(p), &AllocatorMethods{ | ||
New: func(pool unsafe.Pointer, t reflect.Type) reflect.Value { | ||
// If t is Foo, allocate value from the sync pool p. | ||
if t == typeOfFoo { | ||
poolUsed++ // For test only. | ||
|
||
p := (*sync.Pool)(pool) | ||
v := p.Get() | ||
runtime.SetFinalizer(v, func(v *Foo) { | ||
*v = Foo{} | ||
p.Put(v) | ||
}) | ||
|
||
return reflect.ValueOf(v) | ||
} | ||
|
||
// Fallback to reflect API. | ||
return reflect.New(t) | ||
}, | ||
}) | ||
|
||
// Do clone. | ||
target := []*Foo{ | ||
{Bar: 1}, | ||
{Bar: 2}, | ||
} | ||
cloned := allocator.Clone(reflect.ValueOf(target)).Interface().([]*Foo) | ||
|
||
fmt.Println(reflect.DeepEqual(target, cloned)) | ||
fmt.Println(poolUsed) | ||
|
||
// Output: | ||
// true | ||
// 2 | ||
} |
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,86 @@ | ||
// Copyright 2023 Huan Du. All rights reserved. | ||
// Licensed under the MIT license that can be found in the LICENSE file. | ||
|
||
package clone | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
"unsafe" | ||
|
||
"github.com/huandu/go-assert" | ||
) | ||
|
||
func TestAllocatorClone(t *testing.T) { | ||
a := assert.New(t) | ||
cnt := 0 | ||
allocator := NewAllocator(nil, &AllocatorMethods{ | ||
New: func(pool unsafe.Pointer, t reflect.Type) reflect.Value { | ||
cnt++ | ||
return heapNew(pool, t) | ||
}, | ||
}) | ||
|
||
type dataNode struct { | ||
Data int | ||
Next *dataNode | ||
} | ||
data := &dataNode{ | ||
Data: 1, | ||
Next: &dataNode{ | ||
Data: 2, | ||
}, | ||
} | ||
cloned := allocator.Clone(reflect.ValueOf(data)).Interface().(*dataNode) | ||
a.Equal(data, cloned) | ||
|
||
// Should allocate following value. | ||
// - allocator | ||
// - data | ||
// - data.Next | ||
a.Equal(cnt, 3) | ||
} | ||
|
||
func TestAllocatorCloneSlowly(t *testing.T) { | ||
a := assert.New(t) | ||
cnt := 0 | ||
allocator := NewAllocator(nil, &AllocatorMethods{ | ||
New: func(pool unsafe.Pointer, t reflect.Type) reflect.Value { | ||
cnt++ | ||
return heapNew(pool, t) | ||
}, | ||
}) | ||
|
||
type dataNode struct { | ||
Data int | ||
Next *dataNode | ||
} | ||
|
||
// data is a cycle linked list. | ||
data := &dataNode{ | ||
Data: 1, | ||
Next: &dataNode{ | ||
Data: 2, | ||
Next: &dataNode{ | ||
Data: 3, | ||
}, | ||
}, | ||
} | ||
data.Next.Next.Next = data | ||
|
||
cloned := allocator.CloneSlowly(reflect.ValueOf(data)).Interface().(*dataNode) | ||
|
||
a.Equal(data.Data, cloned.Data) | ||
a.Equal(data.Next.Data, cloned.Next.Data) | ||
a.Equal(data.Next.Next.Data, cloned.Next.Next.Data) | ||
a.Equal(data.Next.Next.Next.Data, cloned.Next.Next.Next.Data) | ||
a.Equal(data.Next.Next.Next.Next.Data, cloned.Next.Next.Next.Next.Data) | ||
a.Assert(cloned.Next.Next.Next == cloned) | ||
|
||
// Should allocate following value. | ||
// - allocator | ||
// - data | ||
// - data.Next | ||
// - data.Next.Next | ||
a.Equal(cnt, 4) | ||
} |
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,9 @@ | ||
// Copyright 2023 Huan Du. All rights reserved. | ||
// Licensed under the MIT license that can be found in the LICENSE file. | ||
|
||
//go:build !(go1.20 && goexperiment.arenas) | ||
// +build !go1.20 !goexperiment.arenas | ||
|
||
package clone | ||
|
||
const arenaIsEnabled = false |
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,9 @@ | ||
// Copyright 2023 Huan Du. All rights reserved. | ||
// Licensed under the MIT license that can be found in the LICENSE file. | ||
|
||
//go:build go1.20 && goexperiment.arenas | ||
// +build go1.20,goexperiment.arenas | ||
|
||
package clone | ||
|
||
const arenaIsEnabled = true |
Oops, something went wrong.