stable/pointer, stable/xar
This commit is contained in:
parent
1a56df57f9
commit
1e64e35ccb
4 changed files with 389 additions and 13 deletions
83
stable/pointer.go
Normal file
83
stable/pointer.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package stable
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"git.brut.systems/judah/xx/mem"
|
||||
)
|
||||
|
||||
type Pointer[T any] struct {
|
||||
base unsafe.Pointer
|
||||
pinner runtime.Pinner
|
||||
}
|
||||
|
||||
func PointerPin[T any](ptr *T) (r Pointer[T]) {
|
||||
r.pinner.Pin(ptr)
|
||||
r.base = unsafe.Pointer(ptr)
|
||||
return
|
||||
}
|
||||
|
||||
func PointerCast[TOut, TIn any](p Pointer[TIn]) Pointer[TOut] {
|
||||
return Pointer[TOut]{
|
||||
base: unsafe.Pointer(p.base),
|
||||
pinner: p.pinner,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Pointer[T]) Unpin() {
|
||||
p.pinner.Unpin()
|
||||
p.base = nil
|
||||
}
|
||||
|
||||
func (p Pointer[T]) Pointer() unsafe.Pointer {
|
||||
return p.base
|
||||
}
|
||||
|
||||
func (p Pointer[T]) Address() uintptr {
|
||||
return uintptr(p.base)
|
||||
}
|
||||
|
||||
func (p Pointer[T]) Nil() bool {
|
||||
return p.base == nil
|
||||
}
|
||||
|
||||
func (p Pointer[T]) Add(amount uintptr) Pointer[T] {
|
||||
return Pointer[T]{
|
||||
base: unsafe.Pointer(uintptr(p.base) + amount),
|
||||
pinner: p.pinner,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Pointer[T]) Sub(amount uintptr) Pointer[T] {
|
||||
return Pointer[T]{
|
||||
base: unsafe.Pointer(uintptr(p.base) - amount),
|
||||
pinner: p.pinner,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Pointer[T]) AlignForward(alignment uintptr) Pointer[T] {
|
||||
return Pointer[T]{
|
||||
base: unsafe.Pointer(mem.AlignForward(uintptr(p.base), alignment)),
|
||||
pinner: p.pinner,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Pointer[T]) AlignBackward(alignment uintptr) Pointer[T] {
|
||||
return Pointer[T]{
|
||||
base: unsafe.Pointer(mem.AlignBackward(uintptr(p.base), alignment)),
|
||||
pinner: p.pinner,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Pointer[T]) Load() T {
|
||||
return *(*T)(p.base)
|
||||
}
|
||||
|
||||
func (p Pointer[T]) Store(value T) {
|
||||
*(*T)(p.base) = value
|
||||
}
|
||||
|
||||
func (p Pointer[T]) Nth(index int) T {
|
||||
return p.Add(uintptr(index) * mem.SizeOf[T]()).Load()
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
package pointer
|
||||
package stable_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.brut.systems/judah/xx/stable"
|
||||
)
|
||||
|
||||
func TestAlignForward(t *testing.T) {
|
||||
func TestPointer_AlignForward(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
offset uintptr
|
||||
|
|
@ -19,7 +21,7 @@ func TestAlignForward(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
value := int32(789)
|
||||
pinned := Pin(&value)
|
||||
pinned := stable.PointerPin(&value)
|
||||
defer pinned.Unpin()
|
||||
|
||||
// Add offset to misalign
|
||||
|
|
@ -39,7 +41,7 @@ func TestAlignForward(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAlignBackward(t *testing.T) {
|
||||
func TestPointer_AlignBackward(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
offset uintptr
|
||||
|
|
@ -54,7 +56,7 @@ func TestAlignBackward(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
value := int32(321)
|
||||
pinned := Pin(&value)
|
||||
pinned := stable.PointerPin(&value)
|
||||
defer pinned.Unpin()
|
||||
|
||||
// Add offset to misalign
|
||||
|
|
@ -74,28 +76,28 @@ func TestAlignBackward(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNth(t *testing.T) {
|
||||
func TestPointer_Nth(t *testing.T) {
|
||||
// Test with int32 array
|
||||
arr := []int32{10, 20, 30, 40, 50}
|
||||
pinned := Pin(&arr[0])
|
||||
pinned := stable.PointerPin(&arr[0])
|
||||
defer pinned.Unpin()
|
||||
|
||||
for i := 0; i < len(arr); i++ {
|
||||
value := Nth[int32](pinned, i)
|
||||
value := pinned.Nth(i)
|
||||
if value != arr[i] {
|
||||
t.Errorf("Index %d: expected %d, got %d", i, arr[i], value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNthFloat64(t *testing.T) {
|
||||
func TestPointer_NthFloat64(t *testing.T) {
|
||||
// Test with a float64 array
|
||||
arr := []float64{1.1, 2.2, 3.3, 4.4, 5.5}
|
||||
pinned := Pin(&arr[0])
|
||||
pinned := stable.PointerPin(&arr[0])
|
||||
defer pinned.Unpin()
|
||||
|
||||
for i := 0; i < len(arr); i++ {
|
||||
value := Nth[float64](pinned, i)
|
||||
value := pinned.Nth(i)
|
||||
if value != arr[i] {
|
||||
t.Errorf("Index %d: expected %f, got %f", i, arr[i], value)
|
||||
}
|
||||
|
|
@ -104,7 +106,7 @@ func TestNthFloat64(t *testing.T) {
|
|||
|
||||
func TestPointerArithmeticChain(t *testing.T) {
|
||||
value := int32(888)
|
||||
pinned := Pin(&value)
|
||||
pinned := stable.PointerPin(&value)
|
||||
defer pinned.Unpin()
|
||||
|
||||
// Test chaining operations
|
||||
|
|
@ -115,3 +117,17 @@ func TestPointerArithmeticChain(t *testing.T) {
|
|||
t.Errorf("Expected address %d, got %d", expected, result.Address())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointer_Cast(t *testing.T) {
|
||||
value := int32(123)
|
||||
|
||||
i32 := stable.PointerPin(&value)
|
||||
defer i32.Unpin()
|
||||
|
||||
f32 := stable.PointerCast[float32](i32)
|
||||
f32.Store(3.14)
|
||||
|
||||
if value == 123 {
|
||||
t.Errorf("Value should have been changed")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
174
stable/xar.go
174
stable/xar.go
|
|
@ -1 +1,175 @@
|
|||
package stable
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"math/bits"
|
||||
"unsafe"
|
||||
|
||||
"git.brut.systems/judah/xx"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultChunkSizeShift = 4
|
||||
)
|
||||
|
||||
// Xar is an implementation of Andrew Reece's Xar (Exponential Array).
|
||||
//
|
||||
// See: https://www.youtube.com/watch?v=i-h95QIGchY
|
||||
type Xar[T any] struct {
|
||||
shift uint8
|
||||
count uint64
|
||||
chunks [][]T
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Init() {
|
||||
x.InitWithSize(DefaultChunkSizeShift)
|
||||
}
|
||||
|
||||
func (x *Xar[T]) InitWithSize(size_shift uint8) {
|
||||
if len(x.chunks) != 0 {
|
||||
x.Reset()
|
||||
}
|
||||
|
||||
x.shift = size_shift
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Reset() {
|
||||
for _, c := range x.chunks {
|
||||
clear(c)
|
||||
}
|
||||
|
||||
x.count = 0
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Append(value T) *T {
|
||||
if x.shift == 0 {
|
||||
x.Init()
|
||||
}
|
||||
|
||||
chunk_idx, idx_in_chunk, chunk_cap := x.getChunk(x.count)
|
||||
x.count += 1
|
||||
if chunk_idx >= uint64(len(x.chunks)) {
|
||||
x.chunks = append(x.chunks, make([]T, chunk_cap))
|
||||
}
|
||||
|
||||
slot := &x.chunks[chunk_idx][idx_in_chunk]
|
||||
*slot = value
|
||||
return slot
|
||||
}
|
||||
|
||||
func (x *Xar[T]) AppendMany(values ...T) *T {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
first := x.Append(values[0])
|
||||
|
||||
if len(values) > 1 {
|
||||
for _, v := range values[1:] {
|
||||
x.Append(v)
|
||||
}
|
||||
}
|
||||
|
||||
return first
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Get(index int) *T {
|
||||
chunk_idx, idx_in_chunk, _ := x.getChunk(uint64(index))
|
||||
return &x.chunks[chunk_idx][idx_in_chunk]
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Set(index int, value T) {
|
||||
*x.Get(index) = value
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Remove(index int) {
|
||||
x.Set(index, *x.Get(int(x.count - 1)))
|
||||
x.count -= 1
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Len() int {
|
||||
return int(x.count)
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Cap() (l int) {
|
||||
for _, c := range x.chunks {
|
||||
l += cap(c)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Pointers() iter.Seq2[int, *T] {
|
||||
return func(yield func(int, *T) bool) {
|
||||
idx := -1
|
||||
for chunk_idx, idx_in_chunk := range x.iter() {
|
||||
idx += 1
|
||||
if !yield(idx, &x.chunks[chunk_idx][idx_in_chunk]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Xar[T]) Values() iter.Seq2[int, T] {
|
||||
return func(yield func(int, T) bool) {
|
||||
idx := -1
|
||||
for chunk_idx, idx_in_chunk := range x.iter() {
|
||||
idx += 1
|
||||
if !yield(idx, x.chunks[chunk_idx][idx_in_chunk]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Xar[T]) iter() iter.Seq2[uint64, uint64] {
|
||||
return func(yield func(uint64, uint64) bool) {
|
||||
chunk_size := 1 << x.shift
|
||||
outer:
|
||||
for chunk_idx := range x.chunks {
|
||||
for idx_in_chunk := range chunk_size - 1 {
|
||||
if uint64(chunk_idx+idx_in_chunk) >= uint64(x.count) {
|
||||
break outer
|
||||
}
|
||||
|
||||
if !yield(uint64(chunk_idx), uint64(idx_in_chunk)) {
|
||||
break outer
|
||||
}
|
||||
}
|
||||
|
||||
chunk_size <<= xx.BoolUint(chunk_idx > 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (x *Xar[T]) getChunk(index uint64) (chunk_idx uint64, idx_in_chunk uint64, chunk_cap uint64) {
|
||||
if true /* branchless */ {
|
||||
var (
|
||||
i_shift = index >> x.shift
|
||||
i_shift2 = i_shift != 0
|
||||
msb = msb64(i_shift | 1)
|
||||
b = uint64(*(*uint8)(unsafe.Pointer(&i_shift2)))
|
||||
)
|
||||
|
||||
chunk_idx = msb + b
|
||||
idx_in_chunk = index - (b << (msb + uint64(x.shift)))
|
||||
chunk_cap = 1 << (msb + uint64(x.shift))
|
||||
} else {
|
||||
idx_in_chunk = index
|
||||
chunk_cap = 1 << x.shift
|
||||
|
||||
i_shift := index >> x.shift
|
||||
if i_shift > 0 {
|
||||
chunk_idx = msb64(i_shift | 1)
|
||||
chunk_cap = 1 << (chunk_idx + uint64(x.shift))
|
||||
idx_in_chunk -= chunk_cap
|
||||
chunk_idx += 1
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func msb64(x uint64) uint64 {
|
||||
return uint64(63 - bits.LeadingZeros64(x))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,104 @@
|
|||
package stable
|
||||
package stable_test
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"git.brut.systems/judah/xx/stable"
|
||||
)
|
||||
|
||||
func TestXar_StableWithGC(t *testing.T) {
|
||||
type valuewithptr struct {
|
||||
value int
|
||||
ptr *int
|
||||
}
|
||||
|
||||
var xar stable.Xar[valuewithptr]
|
||||
xar.InitWithSize(8)
|
||||
|
||||
aptr := xar.Append(valuewithptr{value: 10, ptr: nil})
|
||||
bptr := xar.Append(valuewithptr{value: 20, ptr: &aptr.value})
|
||||
|
||||
const N = 1000
|
||||
for i := range N {
|
||||
xar.Append(valuewithptr{value: i})
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
expect(t, xar.Get(0) == aptr)
|
||||
expect(t, xar.Get(1) == bptr)
|
||||
expect(t, xar.Len() == N+2, "len was %d", xar.Len())
|
||||
expect(t, bptr.ptr != nil && bptr.value == 20)
|
||||
expect(t, bptr.ptr == &aptr.value, "%p vs. %p", bptr.ptr, &aptr.value)
|
||||
}
|
||||
|
||||
func TestXar_ResetAndReuse(t *testing.T) {
|
||||
var xar stable.Xar[int]
|
||||
start := xar.Append(60)
|
||||
xar.AppendMany(10, 20, 30, 40, 50)
|
||||
|
||||
xar.Reset()
|
||||
runtime.GC()
|
||||
|
||||
expect(t, xar.Cap() != 0)
|
||||
expect(t, xar.Len() == 0)
|
||||
|
||||
xar.Append(0xFF)
|
||||
xar.Append(0xFC)
|
||||
xar.Append(0xFB)
|
||||
|
||||
expect(t, xar.Get(0) == start)
|
||||
expect(t, xar.Len() == 3)
|
||||
}
|
||||
|
||||
func TestXar_Iterators(t *testing.T) {
|
||||
var xar stable.Xar[int]
|
||||
xar.AppendMany(0, 1, 2, 3, 4, 5)
|
||||
|
||||
iterations := 0
|
||||
for i, v := range xar.Values() {
|
||||
iterations += 1
|
||||
expect(t, v == i, "v: %d, i: %d", v, i)
|
||||
}
|
||||
|
||||
expect(t, iterations == xar.Len())
|
||||
}
|
||||
|
||||
func BenchmarkXar_Append(b *testing.B) {
|
||||
var xar stable.Xar[int]
|
||||
for i := range b.N {
|
||||
xar.Append(i * i)
|
||||
}
|
||||
|
||||
xar.Reset()
|
||||
for i := range b.N {
|
||||
xar.Append(i * i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkXar_RandomAccess(b *testing.B) {
|
||||
var xar stable.Xar[int]
|
||||
for i := range b.N {
|
||||
xar.Append(i * i)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := range b.N {
|
||||
xar.Get(i % 10000)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkXar_Iteration(b *testing.B) {
|
||||
var xar stable.Xar[int]
|
||||
for i := range b.N {
|
||||
xar.Append(i * i)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
sum := 0
|
||||
for _, v := range xar.Values() {
|
||||
sum += v
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue