Why does this lua function cause lag?

Hello all, I had been struggling with some unexplained lag so I had conducted a manual binary tree search of my config, and discovered the lag was coming from a place that surprised me.

In global scope, I define the function

function sethighlight(syntax, opts)
    local bg = opts.bg == nil and "guibg=NONE" or "guibg=" .. opts.bg
    local fg = opts.fg == nil and "guifg=NONE" or "guifg=" .. opts.fg
    -- note that style is a comma separated list e.g. "underline,bold,italic"
    local style = opts.style == nil and "gui=NONE" or "gui=" .. opts.style

    vim.cmd(string.format("hi %s %s %s %s", syntax, bg, fg, style))
end

One of the places I use this, is inside a function I use for galaxyline, in particular a "provider function which I define thusly

local function modified_buffer_indicator(hl)
    return function()
        if vim.bo.modifiable and vim.bo.modified then
            --sethighlight(hl, {fg=colors.red})
            vim.cmd("hi "..hl.." guifg="..colors.red)
            return "▮"
        else
            --sethighlight(hl, {fg=colors.purple})
            vim.cmd("hi "..hl.." guifg="..colors.purple)
            return "▯"
        end
    end
end

The commented lines, much to my surprise, were causing the performance issue. Using the uncommented lines fixes it.

I’m not entirely sure how often galaxy line decides to call this function, but presumably it’s quite a lot, perhaps every the cursor moves. Obviously there’s more I can do to make this more efficient, but I don’t understand why the uncommented lines should be much more efficient than the commented lines.

Immediately after posting this I thought I realized that I was in fact, a moron, and that the obvious reason why this is slow was because lua string interpolation is slow. However, I tried replacing the string interpolation in my function with concatenations, and that did not help. So it may instead have something to do with the particular arguments I’m providing hi.

Anybody have any idea what’s going on here?

No idea really, but what stands out to me is that you’re running guibg=NONE as well as gui=NONE from sethighlight, but not in your direct calls to vim.cmd. Maybe that has some sort of effect?

:highlight command is slow . It does a lot of things . You shouldn’t run it this often.

Good to know. Is there an obvious alternative? I could probably just do it with console codes (though I don’t know if that will interact weirdly with exiting highlight groups), but I’m not aware of any utility functions for doing this so not sure if I have to do it from scratch.