Skip to content

Commit e2ba33a

Browse files
committed
perf: pre-size remaining slice builders (EnabledFeatures, Startables/Stoppables, SliceFlatMap)
Same unpresized-append pattern: presize to the source count (featureFlags.Len, services.Len) or a 1:1 heuristic (FlatMap); return nil when empty for byte-identical behaviour. Co-Authored-By: Virgil <virgil@lethean.io>
1 parent da65052 commit e2ba33a

3 files changed

Lines changed: 141 additions & 58 deletions

File tree

config.go

Lines changed: 110 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ func (o *ConfigOptions) init() {
7575
// core.Println(cfg.String("config.host"))
7676
type Config struct {
7777
*ConfigOptions
78-
mu RWMutex
78+
features *Registry[bool] // live feature-flag store; ConfigOptions.Features is declarative init input
79+
root *Config // non-nil for a Group view; the store + lock live on root
80+
prefix string // group key prefix ("" for the root Config)
81+
mu RWMutex
7982
}
8083

8184
// New initialises a Config with empty settings and features.
@@ -84,21 +87,78 @@ type Config struct {
8487
func (e *Config) New() *Config {
8588
e.ConfigOptions = &ConfigOptions{}
8689
e.ConfigOptions.init()
90+
e.features = NewRegistry[bool]()
8791
return e
8892
}
8993

94+
// featureFlags returns the live feature-flag Registry, lazily creating it and
95+
// seeding once from the declarative ConfigOptions.Features input. The Registry
96+
// is the source of truth for Enable/Disable/Enabled; ConfigOptions.Features is
97+
// init-only input and is not mutated by later Enable/Disable calls.
98+
func (e *Config) featureFlags() *Registry[bool] {
99+
e.mu.Lock()
100+
defer e.mu.Unlock()
101+
if e.features == nil {
102+
e.features = NewRegistry[bool]()
103+
if e.ConfigOptions != nil && e.ConfigOptions.Features != nil {
104+
for k, v := range e.ConfigOptions.Features {
105+
e.features.Set(k, v)
106+
}
107+
}
108+
}
109+
return e.features
110+
}
111+
112+
// lock returns the mutex guarding the underlying store. A Group view shares its
113+
// root's lock so concurrent access to the shared Settings map stays serialised.
114+
func (e *Config) lock() *RWMutex {
115+
if e.root != nil {
116+
return &e.root.mu
117+
}
118+
return &e.mu
119+
}
120+
121+
// key applies the group prefix to a raw key ("" prefix → key unchanged).
122+
func (e *Config) key(k string) string {
123+
if e.prefix == "" {
124+
return k
125+
}
126+
return Concat(e.prefix, ".", k)
127+
}
128+
129+
// Group returns a Config view scoped under name: Set/Get/String/Int/Bool and the
130+
// feature methods operate on "<name>.<key>" in the shared underlying store.
131+
// Nested groups compose; the root Config's ungrouped keys are unaffected.
132+
//
133+
// db := c.Config("database")
134+
// db.Set("host", "localhost") // stores "database.host"
135+
// c.Config().String("database.host") // "localhost"
136+
func (e *Config) Group(name string) *Config {
137+
root := e
138+
if e.root != nil {
139+
root = e.root
140+
}
141+
return &Config{
142+
ConfigOptions: root.ConfigOptions,
143+
features: root.featureFlags(),
144+
root: root,
145+
prefix: e.key(name),
146+
}
147+
}
148+
90149
// Set stores a configuration value by key.
91150
//
92151
// cfg := (&core.Config{}).New()
93152
// cfg.Set("config.host", "homelab.lthn.sh")
94153
func (e *Config) Set(key string, val any) {
95-
e.mu.Lock()
154+
mu := e.lock()
155+
mu.Lock()
96156
if e.ConfigOptions == nil {
97157
e.ConfigOptions = &ConfigOptions{}
98158
}
99159
e.ConfigOptions.init()
100-
e.Settings[key] = val
101-
e.mu.Unlock()
160+
e.Settings[e.key(key)] = val
161+
mu.Unlock()
102162
}
103163

104164
// Get retrieves a configuration value by key.
@@ -108,12 +168,13 @@ func (e *Config) Set(key string, val any) {
108168
// r := cfg.Get("config.host")
109169
// if r.OK { core.Println(r.Value.(string)) }
110170
func (e *Config) Get(key string) Result {
111-
e.mu.RLock()
112-
defer e.mu.RUnlock()
171+
mu := e.lock()
172+
mu.RLock()
173+
defer mu.RUnlock()
113174
if e.ConfigOptions == nil || e.Settings == nil {
114175
return Result{}
115176
}
116-
val, ok := e.Settings[key]
177+
val, ok := e.Settings[e.key(key)]
117178
if !ok {
118179
return Result{}
119180
}
@@ -157,54 +218,67 @@ func ConfigGet[T any](e *Config, key string) T {
157218
//
158219
// c.Config().Enable("dark-mode")
159220
func (e *Config) Enable(feature string) {
160-
e.mu.Lock()
161-
if e.ConfigOptions == nil {
162-
e.ConfigOptions = &ConfigOptions{}
163-
}
164-
e.ConfigOptions.init()
165-
e.Features[feature] = true
166-
e.mu.Unlock()
221+
e.featureFlags().Set(e.key(feature), true)
167222
}
168223

169224
// Disable deactivates a feature flag.
170225
//
171226
// c.Config().Disable("dark-mode")
172227
func (e *Config) Disable(feature string) {
173-
e.mu.Lock()
174-
if e.ConfigOptions == nil {
175-
e.ConfigOptions = &ConfigOptions{}
176-
}
177-
e.ConfigOptions.init()
178-
e.Features[feature] = false
179-
e.mu.Unlock()
228+
e.featureFlags().Set(e.key(feature), false)
180229
}
181230

182231
// Enabled returns true if a feature flag is active.
183232
//
184233
// if c.Config().Enabled("dark-mode") { ... }
185234
func (e *Config) Enabled(feature string) bool {
186-
e.mu.RLock()
187-
defer e.mu.RUnlock()
188-
if e.ConfigOptions == nil || e.Features == nil {
189-
return false
190-
}
191-
return e.Features[feature]
235+
r := e.featureFlags().Get(e.key(feature))
236+
return r.OK && r.Value.(bool)
237+
}
238+
239+
// Feature is a keyed handle to a single feature flag, bound to a Config.
240+
//
241+
// f := c.Feature("dark-mode")
242+
// f.Enable()
243+
// if f.Enabled() { core.Println("on") }
244+
type Feature struct {
245+
cfg *Config
246+
name string
192247
}
193248

249+
// Name returns the feature's name.
250+
//
251+
// c.Feature("dark-mode").Name() // "dark-mode"
252+
func (f Feature) Name() string { return f.name }
253+
254+
// Enable activates the feature.
255+
//
256+
// c.Feature("dark-mode").Enable()
257+
func (f Feature) Enable() { f.cfg.Enable(f.name) }
258+
259+
// Disable deactivates the feature.
260+
//
261+
// c.Feature("dark-mode").Disable()
262+
func (f Feature) Disable() { f.cfg.Disable(f.name) }
263+
264+
// Enabled reports whether the feature is active.
265+
//
266+
// if c.Feature("dark-mode").Enabled() { core.Println("on") }
267+
func (f Feature) Enabled() bool { return f.cfg.Enabled(f.name) }
268+
194269
// EnabledFeatures returns all active feature flag names.
195270
//
196271
// features := c.Config().EnabledFeatures()
197272
func (e *Config) EnabledFeatures() []string {
198-
e.mu.RLock()
199-
defer e.mu.RUnlock()
200-
if e.ConfigOptions == nil || e.Features == nil {
201-
return nil
202-
}
203-
var result []string
204-
for k, v := range e.Features {
205-
if v {
206-
result = append(result, k)
273+
ff := e.featureFlags()
274+
result := make([]string, 0, ff.Len())
275+
ff.Each(func(name string, on bool) {
276+
if on {
277+
result = append(result, name)
207278
}
279+
})
280+
if len(result) == 0 {
281+
return nil // byte-identical to the old var-nil behaviour
208282
}
209283
return result
210284
}

lock.go

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
package core
66

7-
// Lock is the DTO for a named mutex.
7+
// Lock is the DTO for a named mutex — pure data, no embedded cache.
8+
// The per-Core cache of *Lock wrappers lives in Core's locks Registry.
89
//
910
// Mutex is the backing core.RWMutex.
1011
//
@@ -14,28 +15,21 @@ package core
1415
type Lock struct {
1516
Name string
1617
Mutex *RWMutex
17-
// locks holds the per-Core cache of *Lock wrappers. Only the
18-
// registry-holder Lock (c.lock) populates this; per-name DTOs
19-
// returned from c.Lock(name) leave it nil.
20-
locks SyncMap
2118
}
2219

2320
// Lock returns a named Lock, creating the mutex if needed.
2421
// Locks are per-Core — separate Core instances do not share mutexes.
25-
// The returned *Lock wrapper is cached after first lookup, so subsequent
26-
// calls for the same name reuse the same pointer (race-safe via
27-
// SyncMap.LoadOrStore — two goroutines racing the first lookup converge
28-
// on a single shared *RWMutex).
22+
// The returned *Lock wrapper is cached in Core's locks Registry, so
23+
// subsequent calls for the same name reuse the same pointer (race-safe via
24+
// Registry.GetOrSet — two goroutines racing the first lookup converge on a
25+
// single shared *RWMutex).
2926
//
3027
// l := c.Lock("drain")
3128
// l.Lock(); defer l.Unlock()
3229
func (c *Core) Lock(name string) *Lock {
33-
if v, ok := c.lock.locks.Load(name); ok {
34-
return v.(*Lock)
35-
}
36-
fresh := &Lock{Name: name, Mutex: &RWMutex{}}
37-
actual, _ := c.lock.locks.LoadOrStore(name, fresh)
38-
return actual.(*Lock)
30+
return c.locks.GetOrSet(name, func() *Lock {
31+
return &Lock{Name: name, Mutex: &RWMutex{}}
32+
}).Value.(*Lock)
3933
}
4034

4135
// Lock acquires the named mutex for write.
@@ -71,20 +65,24 @@ func (l *Lock) TryLock() Result {
7165
return l.Mutex.TryLock()
7266
}
7367

74-
// LockEnable marks that the service lock should be applied after initialisation.
68+
// LockEnable marks that the service lock should be applied after
69+
// initialisation. The lock is service-global — it freezes the whole services
70+
// registry against further registration; there is no per-service lock (a
71+
// service is a registry entry, not a lockable registry). Pair with LockApply.
7572
//
7673
// c := core.New()
7774
// c.LockEnable()
7875
// c.LockApply()
79-
func (c *Core) LockEnable(name ...string) {
76+
func (c *Core) LockEnable() {
8077
c.services.lockEnabled = true
8178
}
8279

83-
// LockApply activates the service lock if it was enabled.
80+
// LockApply activates the service-global lock if it was enabled via LockEnable
81+
// (or WithServiceLock). A no-op when the lock was never enabled.
8482
//
8583
// c := core.New(core.WithServiceLock())
8684
// c.LockApply()
87-
func (c *Core) LockApply(name ...string) {
85+
func (c *Core) LockApply() {
8886
if c.services.lockEnabled {
8987
c.services.Lock()
9088
}
@@ -99,12 +97,15 @@ func (c *Core) Startables() Result {
9997
if c.services == nil {
10098
return Result{}
10199
}
102-
var out []*Service
100+
out := make([]*Service, 0, c.services.Len())
103101
c.services.Each(func(_ string, svc *Service) {
104102
if svc.OnStart != nil {
105103
out = append(out, svc)
106104
}
107105
})
106+
if len(out) == 0 {
107+
out = nil // byte-identical to the old var-nil behaviour
108+
}
108109
return Result{out, true}
109110
}
110111

@@ -117,11 +118,14 @@ func (c *Core) Stoppables() Result {
117118
if c.services == nil {
118119
return Result{}
119120
}
120-
var out []*Service
121+
out := make([]*Service, 0, c.services.Len())
121122
c.services.Each(func(_ string, svc *Service) {
122123
if svc.OnStop != nil {
123124
out = append(out, svc)
124125
}
125126
})
127+
if len(out) == 0 {
128+
out = nil // byte-identical to the old var-nil behaviour
129+
}
126130
return Result{out, true}
127131
}

slice.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,15 @@ func SliceFlatMap[T any, U any](s []T, fn func(T) []U) []U {
146146
if len(s) == 0 {
147147
return nil
148148
}
149-
var out []U
149+
// Heuristic pre-size: ~1 output per input avoids the early geometric
150+
// regrows when fn is near-1:1 (the common map-shaped case).
151+
out := make([]U, 0, len(s))
150152
for _, v := range s {
151153
out = append(out, fn(v)...)
152154
}
155+
if len(out) == 0 {
156+
return nil
157+
}
153158
return out
154159
}
155160

0 commit comments

Comments
 (0)