Skip to content
darxnetPublic

About

Generates Reset() methods for pooled Go structs: back to the zero state, memory kept

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

goreset

Go Reference Go Coverage

goreset generates Reset() methods for structs that are reused through a sync.Pool or a free list: the value goes back to its zero state, but keeps the memory it owns.

Why goreset?

A pooled object has to be cleaned before reuse. *r = T{} is the fast way to do it, and the wrong one: it drops every slice's backing array, forgets a reusable *time.Timer, and does not compile for a struct holding a mutex. A hand-written Reset keeps all of that — until a new field is added and forgotten there. goreset writes the method from the struct definition, so it is always complete, and chooses the cheapest form for the type:

// Code generated by goreset; DO NOT EDIT.

// Reset zeroes r in one assignment and gives the slices their arrays back.
func (r *Request) Reset() {
	clear(r.Data)
	keepData := r.Data[:0]
	keepTimer := r.Timer

	*r = Request{}
	r.Data = keepData
	r.Mask = ^uint32(0)
	r.Timer = keepTimer
	if r.Timer != nil {
		r.Timer.Stop()
	}
}

What it does per field

Field Reset
slice clear(s); s = s[:0] — the backing array is kept
pointer, map, chan, interface, func nil
numbers, bool, string, arrays, plain structs zero value
value with its own Reset() r.F.Reset()
pointer with its own Reset() kept; r.F.Reset() when not nil
atomic.* Store(0) / Store(false) / Store(nil)
sync.* (Mutex, WaitGroup, Pool...) left alone

Struct tags change the treatment of a field:

Tag Effect
reset:"-" the field is left as it is
reset:"value=EXPR" set to EXPR instead of zero, e.g. value=^uint32(0)
reset:"value=pkg.Const,import=example.com/pkg" the same with an import the expression needs
reset:"call=Method" kept; r.F.Method() is called (call=Stop for a *time.Timer)
reset:"release=Func" pointer with Reset(): Func(r.F) is called and the field set to nil

Two modes

Bulk: the fields to keep are saved, the struct is zeroed in one assignment, the kept fields are put back. One memclr for the whole struct, no per-field code for the zeroed ones.

Field by field: one statement per field; sync fields are not touched.

The mode is chosen per type by a cost model, in units of one field store:

bulk field by field
the struct memclr: 1 per 16 bytes —
plain field (number, pointer, string, atomic...) 0 1
slice 3 (clear, keep, restore) 2 (clear, reslice)
value with Reset() 2 + 2 per 16 bytes (copied out and back) 1 (the call)
pointer with Reset(), call=, reset:"-" 2 (keep, restore) 1
value= 1 1
array, struct without Reset() 0 1 per 16 bytes

The cheaper mode is generated; a sync field forces field-wise mode whatever the costs. The constants were fitted on two real structs (700 bytes / 95 fields: both modes equal; 2400 bytes / 74 fields: field-wise 2.5x faster) — a model, not a measurement, so a struct near the break-even point is worth a benchmark of its own.

The comment of the generated method says which mode and why:

// Reset zeroes r field by field, not in one assignment:
//   - zeroing all 2400 bytes and restoring 39 fields costs 314, resetting the 74 fields one by one costs 162

and the generator says so on stderr too, with what would change the outcome:

goreset: wrote reqvars_reset.gen.go
goreset: ReqVars is reset field by field; for one assignment:
  - ReqVars: one assignment would zero all 2400 bytes and restore 39 fields (cost 314) where resetting the 74 fields one by one costs 162; fewer slices and kept values, or a smaller struct, would turn that around

For a lock the hint is to move it behind a pointer or out of the pooled value. Package.Analyze returns the mode, both costs and the hints to other tools.

Installation

go get -tool github.com/darxnet/goreset/cmd/goreset

Requires Go 1.24 (go get -tool). No dependencies: the package is loaded with go list -export and go/importer from the standard library.

Usage

Put the directive next to the type and run go generate:

//go:generate go tool goreset -type=Request,Response
type Request struct {
	Data  []byte
	Timer *time.Timer `reset:"call=Stop"`
	Mask  uint32      `reset:"value=^uint32(0)"`
	Mu    sync.Mutex
}

This writes request_reset.gen.go and response_reset.gen.go in the same package. -dir points the generator at another package.

The generator is also a library — goreset.Load and Package.Generate — for tools that want the source without the file.

Rules of the generated Reset

  • It must run without concurrent users of the value: atomics are stored, not swapped, and bulk mode copies the struct.
  • Slices keep their capacity. A field that must not keep memory between uses (a buffer that may grow very large once) should be tagged reset:"-" and handled by hand, or released through release=.
  • A nested value's Reset() is called after the bulk assignment, so it sees its own state, not zeroes.

See also

The sibling modules follow the same rule — nothing allocated on the hot path, the zero value ready to use:

  • goshard — sharded, lock-based concurrent map for high-churn data: stores allocate nothing
  • gosync — generic concurrent map with sync.Map semantics and no interface boxing, for read-mostly data
  • gocopy — generates CopyFrom/Copy that reuse the destination's memory instead of allocating

License

Apache License 2.0. See LICENSE for details.

About

Generates Reset() methods for pooled Go structs: back to the zero state, memory kept

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages