-
Notifications
You must be signed in to change notification settings - Fork 4
β‘ Bolt: Cache color map to reduce allocations during plugin execution #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ## 2026-05-02 - Cache Reference Types During Parallel Plugin Execution | ||
| **Learning:** In performance-sensitive areas with parallel execution like `statusline.go`'s `runPlugin`, dynamically calling `colors.ColorMap()` allocates a new, relatively large map for every plugin invocation. Repeatedly allocating maps inside hot loops or goroutines creates unnecessary memory churn and GC pressure. | ||
| **Action:** Always cache static or read-only maps at the instance level (e.g., using `sync.Once` inside the struct) so they are only allocated once per rendering cycle, rather than re-creating them inside functions that execute concurrently across multiple routines. |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -35,6 +35,8 @@ type StatusLine struct { | |||||||||||
| isIdle bool | ||||||||||||
| bashPlugins []plugin.Plugin // Cached discovered bash plugins | ||||||||||||
| bashPluginsOnce sync.Once | ||||||||||||
| colorMap map[string]string // Cached color map | ||||||||||||
| colorMapOnce sync.Once | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // New creates a new StatusLine renderer | ||||||||||||
|
|
@@ -634,7 +636,7 @@ func (sl *StatusLine) runPlugin(name string) string { | |||||||||||
| ContextWindowSize: sl.input.Context.ContextWindow, | ||||||||||||
| }, | ||||||||||||
| Config: sl.getPluginConfig(name), | ||||||||||||
| Colors: colors.ColorMap(), | ||||||||||||
| Colors: sl.getColorMap(), | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // Try native plugin first (much faster - no subprocess) | ||||||||||||
|
|
@@ -707,3 +709,11 @@ func (sl *StatusLine) getPluginConfig(name string) map[string]any { | |||||||||||
| pluginCfg := sl.config.LoadPluginConfig(name) | ||||||||||||
| return map[string]any{name: pluginCfg} | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // getColorMap returns the color map, creating it once if needed | ||||||||||||
| func (sl *StatusLine) getColorMap() map[string]string { | ||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sharing a single map instance across multiple plugins executed in parallel introduces a concurrency risk. In Go, maps are not thread-safe for concurrent operations if at least one of them is a write. While plugins are generally expected to treat the
Suggested change
|
||||||||||||
| sl.colorMapOnce.Do(func() { | ||||||||||||
| sl.colorMap = colors.ColorMap() | ||||||||||||
| }) | ||||||||||||
| return sl.colorMap | ||||||||||||
| } | ||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the color map is derived from static constants and does not depend on any instance-specific state, it is more efficient to cache it at the package level rather than the instance level. This ensures the map is allocated exactly once for the lifetime of the process, regardless of how many
StatusLineinstances are created. This is particularly relevant ifStatusLineis instantiated frequently (e.g., once per render cycle).