perf: reduce calls to GetUiConfig

GetUiConfig was being called many times, and came up as a high CPU user
in a cpuprofile. Every call would merge a UIConfig, which is a costly
operation. Ideally, we would only need to have a config for every
account X every directory. We also have a context for subjects. This
patch stores all FOLDER and ACCOUNT level configs and reuses those
merged objects. The SUBJECT contexts are not stored in favor of merging
on-the-go, with a TODO comment to deprecate that feature and implement a
better per-message styling option. I suspect this feature is not used
very much.

Before applying this patch with my setup, GetUiConfig is called 1159
times just to open aerc. After applying, this is reduced to 37.

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
Tim Culverhouse 2022-07-03 10:11:13 -05:00 committed by Robin Jarry
parent d45c07eb6a
commit 4240f1fbfd
4 changed files with 36 additions and 14 deletions

View file

@ -1027,6 +1027,10 @@ func (config AercConfig) GetUiConfig(params map[ContextType]string) *UIConfig {
return &baseUi return &baseUi
} }
func (config *AercConfig) GetContextualUIConfigs() []UIConfigContext {
return config.ContextualUis
}
func (uiConfig UIConfig) GetStyle(so StyleObject) tcell.Style { func (uiConfig UIConfig) GetStyle(so StyleObject) tcell.Style {
return uiConfig.style.Get(so) return uiConfig.style.Get(so)
} }

View file

@ -34,17 +34,14 @@ type AccountView struct {
worker *types.Worker worker *types.Worker
state *statusline.State state *statusline.State
newConn bool // True if this is a first run after a new connection/reconnection newConn bool // True if this is a first run after a new connection/reconnection
uiConf *config.UIConfig
} }
func (acct *AccountView) UiConfig() *config.UIConfig { func (acct *AccountView) UiConfig() *config.UIConfig {
var folder string
if dirlist := acct.Directories(); dirlist != nil { if dirlist := acct.Directories(); dirlist != nil {
folder = dirlist.Selected() return dirlist.UiConfig()
} }
return acct.conf.GetUiConfig(map[config.ContextType]string{ return acct.uiConf
config.UI_CONTEXT_ACCOUNT: acct.AccountConfig().Name,
config.UI_CONTEXT_FOLDER: folder,
})
} }
func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountConfig, func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountConfig,
@ -61,6 +58,7 @@ func NewAccountView(aerc *Aerc, conf *config.AercConfig, acct *config.AccountCon
host: host, host: host,
logger: logger, logger: logger,
state: statusline.NewState(acct.Name, len(conf.Accounts) > 1, conf.Statusline), state: statusline.NewState(acct.Name, len(conf.Accounts) > 1, conf.Statusline),
uiConf: acctUiConf,
} }
view.grid = ui.NewGrid().Rows([]ui.GridSpec{ view.grid = ui.NewGrid().Rows([]ui.GridSpec{

View file

@ -44,6 +44,8 @@ type DirectoryLister interface {
SetMsgStore(string, *lib.MessageStore) SetMsgStore(string, *lib.MessageStore)
FilterDirs([]string, []string, bool) []string FilterDirs([]string, []string, bool) []string
UiConfig() *config.UIConfig
} }
type DirectoryList struct { type DirectoryList struct {
@ -61,6 +63,7 @@ type DirectoryList struct {
skipSelect context.Context skipSelect context.Context
skipSelectCancel context.CancelFunc skipSelectCancel context.CancelFunc
connected bool connected bool
uiConf map[string]*config.UIConfig
} }
func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig, func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig,
@ -68,6 +71,8 @@ func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig,
) DirectoryLister { ) DirectoryLister {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
uiConfMap := make(map[string]*config.UIConfig)
dirlist := &DirectoryList{ dirlist := &DirectoryList{
aercConf: conf, aercConf: conf,
acctConf: acctConf, acctConf: acctConf,
@ -76,6 +81,7 @@ func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig,
worker: worker, worker: worker,
skipSelect: ctx, skipSelect: ctx,
skipSelectCancel: cancel, skipSelectCancel: cancel,
uiConf: uiConfMap,
} }
uiConf := dirlist.UiConfig() uiConf := dirlist.UiConfig()
dirlist.spinner = NewSpinner(uiConf) dirlist.spinner = NewSpinner(uiConf)
@ -92,10 +98,15 @@ func NewDirectoryList(conf *config.AercConfig, acctConf *config.AccountConfig,
} }
func (dirlist *DirectoryList) UiConfig() *config.UIConfig { func (dirlist *DirectoryList) UiConfig() *config.UIConfig {
return dirlist.aercConf.GetUiConfig(map[config.ContextType]string{ if ui, ok := dirlist.uiConf[dirlist.Selected()]; ok {
return ui
}
ui := dirlist.aercConf.GetUiConfig(map[config.ContextType]string{
config.UI_CONTEXT_ACCOUNT: dirlist.acctConf.Name, config.UI_CONTEXT_ACCOUNT: dirlist.acctConf.Name,
config.UI_CONTEXT_FOLDER: dirlist.Selected(), config.UI_CONTEXT_FOLDER: dirlist.Selected(),
}) })
dirlist.uiConf[dirlist.Selected()] = ui
return ui
} }
func (dirlist *DirectoryList) List() []string { func (dirlist *DirectoryList) List() []string {

View file

@ -194,14 +194,23 @@ func (ml *MessageList) drawRow(textWidth int, ctx *ui.Context, uid uint32, row i
return false return false
} }
// TODO deprecate subject contextual UIs? Only related setting is styleset,
// should implement a better per-message styling method
// Check if we have any applicable ContextualUIConfigs
confs := ml.aerc.conf.GetContextualUIConfigs()
uiConfig := acct.Directories().UiConfig()
for _, c := range confs {
if c.ContextType == config.UI_CONTEXT_SUBJECT && msg.Envelope != nil {
if c.Regex.Match([]byte(msg.Envelope.Subject)) {
confParams := map[config.ContextType]string{ confParams := map[config.ContextType]string{
config.UI_CONTEXT_ACCOUNT: acct.AccountConfig().Name, config.UI_CONTEXT_ACCOUNT: acct.AccountConfig().Name,
config.UI_CONTEXT_FOLDER: acct.Directories().Selected(), config.UI_CONTEXT_FOLDER: acct.Directories().Selected(),
config.UI_CONTEXT_SUBJECT: msg.Envelope.Subject,
}
uiConfig = ml.conf.GetUiConfig(confParams)
}
} }
if msg.Envelope != nil {
confParams[config.UI_CONTEXT_SUBJECT] = msg.Envelope.Subject
} }
uiConfig := ml.conf.GetUiConfig(confParams)
msg_styles := []config.StyleObject{} msg_styles := []config.StyleObject{}
// unread message // unread message