forked from joejulian/gio
op: don't allocate for each string reference
When storing a string in an interface value that escapes, Go has to heap allocate space for the string header, as interface values can only store pointers. In text-heavy applications, this can lead to hundreds of allocations per frame due to semantic.LabelOp, the primary user of string-typed references in ops. Instead of allocating each string header individually, provide a slice of strings to store string-typed references in, and store pointers into this slice as the actual references. This only allocates when resizing the slice's backing array, and averages out to no allocations, as the backing array gets reused between calls to Ops.Reset. We introduce two new functions, Write1String and Write2String, which make use of this new slice for their last argument. We could've automated this in the existing Write1 and Write2 methods, but that would require type assertions on each call, and the vast majority of ops do not make use of strings. Signed-off-by: Dominik Honnef <dominik@honnef.co>
This commit is contained in:
committed by
Elias Naur
parent
b9654eb4eb
commit
b4d93379c4
@@ -19,6 +19,17 @@ type Ops struct {
|
||||
data []byte
|
||||
// refs hold external references for operations.
|
||||
refs []interface{}
|
||||
// stringRefs provides space for string references, pointers to which will
|
||||
// be stored in refs. Storing a string directly in refs would cause a heap
|
||||
// allocation, to store the string header in an interface value. The backing
|
||||
// array of stringRefs, on the other hand, gets reused between calls to
|
||||
// reset, making string references free on average.
|
||||
//
|
||||
// Appending to stringRefs might reallocate the backing array, which will
|
||||
// leave pointers to the old array in refs. This temporarily causes a slight
|
||||
// increase in memory usage, but this, too, amortizes away as the capacity
|
||||
// of stringRefs approaches its stable maximum.
|
||||
stringRefs []string
|
||||
// nextStateID is the id allocated for the next
|
||||
// StateOp.
|
||||
nextStateID int
|
||||
@@ -183,8 +194,12 @@ func Reset(o *Ops) {
|
||||
for i := range o.refs {
|
||||
o.refs[i] = nil
|
||||
}
|
||||
for i := range o.stringRefs {
|
||||
o.stringRefs[i] = ""
|
||||
}
|
||||
o.data = o.data[:0]
|
||||
o.refs = o.refs[:0]
|
||||
o.stringRefs = o.stringRefs[:0]
|
||||
o.nextStateID = 0
|
||||
o.version++
|
||||
}
|
||||
@@ -265,12 +280,26 @@ func Write1(o *Ops, n int, ref1 interface{}) []byte {
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write1String(o *Ops, n int, ref1 string) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.stringRefs = append(o.stringRefs, ref1)
|
||||
o.refs = append(o.refs, &o.stringRefs[len(o.stringRefs)-1])
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write2(o *Ops, n int, ref1, ref2 interface{}) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2)
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write2String(o *Ops, n int, ref1 interface{}, ref2 string) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.stringRefs = append(o.stringRefs, ref2)
|
||||
o.refs = append(o.refs, ref1, &o.stringRefs[len(o.stringRefs)-1])
|
||||
return o.data[len(o.data)-n:]
|
||||
}
|
||||
|
||||
func Write3(o *Ops, n int, ref1, ref2, ref3 interface{}) []byte {
|
||||
o.data = append(o.data, make([]byte, n)...)
|
||||
o.refs = append(o.refs, ref1, ref2, ref3)
|
||||
|
||||
Reference in New Issue
Block a user