Skip to content

Commit 10450a1

Browse files
committed
LNumber2I optimisations
remove unused slice from each allocator page (saves ~512 bytes per page) stop temp value escaping to heap and creating garbage
1 parent fbb04a0 commit 10450a1

File tree

1 file changed

+33
-27
lines changed

1 file changed

+33
-27
lines changed

alloc.go

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,52 +26,58 @@ func init() {
2626

2727
// allocator is a fast bulk memory allocator for the LValue.
2828
type allocator struct {
29-
top int
30-
size int
31-
nptrs []LValue
32-
nheader *reflect.SliceHeader
33-
fptrs []float64
34-
fheader *reflect.SliceHeader
35-
itabLNumber unsafe.Pointer
29+
top int
30+
size int
31+
fptrs []float64
32+
fheader *reflect.SliceHeader
33+
34+
scratchValue LValue
35+
scratchValueP *iface
3636
}
3737

3838
func newAllocator(size int) *allocator {
3939
al := &allocator{
40-
top: 0,
41-
size: size,
42-
nptrs: make([]LValue, size),
43-
nheader: nil,
44-
fptrs: make([]float64, size),
45-
fheader: nil,
46-
itabLNumber: unsafe.Pointer(nil),
40+
top: 0,
41+
size: size,
42+
fptrs: make([]float64, size),
43+
fheader: nil,
4744
}
48-
al.nheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.nptrs))
4945
al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs))
46+
al.scratchValue = LNumber(0)
47+
al.scratchValueP = (*iface)(unsafe.Pointer(&al.scratchValue))
5048

51-
var v LValue = LNumber(0)
52-
vp := (*iface)(unsafe.Pointer(&v))
53-
al.itabLNumber = vp.itab
5449
return al
5550
}
5651

52+
// LNumber2I takes a number value and returns an interface LValue representing the same number.
53+
// Converting an LNumber to a LValue naively, by doing:
54+
// `var val LValue = myLNumber`
55+
// will result in an individual heap alloc of 8 bytes for the float value. LNumber2I amortizes the cost and memory
56+
// overhead of these allocs by allocating blocks of floats instead.
57+
// The downside of this is that all of the floats on a given block have to become eligible for gc before the block
58+
// as a whole can be gc-ed.
5759
func (al *allocator) LNumber2I(v LNumber) LValue {
60+
// first check for shared preloaded numbers
5861
if v >= 0 && v < preloadLimit && float64(v) == float64(int64(v)) {
5962
return preloads[int(v)]
6063
}
61-
if al.top == len(al.nptrs)-1 {
64+
65+
// check if we need a new alloc page
66+
if al.top == len(al.fptrs)-1 {
6267
al.top = 0
63-
al.nptrs = make([]LValue, al.size)
64-
al.nheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.nptrs))
6568
al.fptrs = make([]float64, al.size)
6669
al.fheader = (*reflect.SliceHeader)(unsafe.Pointer(&al.fptrs))
6770
}
71+
72+
// alloc a new float, and store our value into it
6873
fptr := (*float64)(unsafe.Pointer(al.fheader.Data + uintptr(al.top)*unsafe.Sizeof(_fv)))
69-
e := *(*LValue)(unsafe.Pointer(al.nheader.Data + uintptr(al.top)*unsafe.Sizeof(_uv)))
7074
al.top++
71-
72-
ep := (*iface)(unsafe.Pointer(&e))
73-
ep.itab = al.itabLNumber
7475
*fptr = float64(v)
75-
ep.word = unsafe.Pointer(fptr)
76-
return e
76+
77+
// hack our scratch LValue to point to our allocated value
78+
// this scratch lvalue is copied when this function returns meaning the scratch value can be reused
79+
// on the next call
80+
al.scratchValueP.word = unsafe.Pointer(fptr)
81+
82+
return al.scratchValue
7783
}

0 commit comments

Comments
 (0)