perf(lsp): clear table by table.clear() #39222

benchmark: https://gist.github.com/ofseed/6224529d77c016c36f7ab2f977059848

    local rounds = tonumber(arg[1]) or 1000
    local count = tonumber(arg[2]) or 1000

    -- Load the table.clear function.
    local clear = require("table.clear")

    local function fill(t, n)
      for i = 1, n do
        t[i] = i
      end
    end

    local function bench_reassign(n_rounds, n_items)
      local t = {}
      local start = os.clock()

      for _ = 1, n_rounds do
        t = {}
        collectgarbage("collect")
        fill(t, n_items)
      end

      return os.clock() - start
    end

    local function bench_reassign_no_gc(n_rounds, n_items)
      local t = {}
      local start = os.clock()

      for _ = 1, n_rounds do
        t = {}
        fill(t, n_items)
      end

      return os.clock() - start
    end

    local function bench_clear(n_rounds, n_items)
      local t = {}
      local start = os.clock()

      for _ = 1, n_rounds do
        clear(t)
        fill(t, n_items)
      end

      return os.clock() - start
    end

    -- Warm up LuaJIT before the real benchmark.
    do
      local t = {}
      for _ = 1, 2000 do
        clear(t)
        fill(t, count)
      end
    end

    collectgarbage("collect")

    local reassign_time = bench_reassign(rounds, count)
    collectgarbage("collect")

    local reassign_no_gc_time = bench_reassign_no_gc(rounds, count)
    collectgarbage("collect")

    local clear_time = bench_clear(rounds, count)

    print(string.format("rounds=%d count=%d", rounds, count))
    print(string.format("t = {} + GC   : %.6f s", reassign_time))
    print(string.format("t = {}        : %.6f s", reassign_no_gc_time))
    print(string.format("table.clear   : %.6f s", clear_time))
    print(string.format("vs + GC       : %.2fx", reassign_time / clear_time))
    print(string.format("vs no GC      : %.2fx", reassign_no_gc_time / clear_time))

benchmark result:

    rounds=1000 count=1000
    t = {} + GC   : 0.022469 s
    t = {}        : 0.002570 s
    table.clear   : 0.000387 s
    vs + GC       : 58.06x
    vs no GC      : 6.64x

`count` is how many items the table has, and `round` is how many rounds we fill
the table, clear, and then refill it. `table = {}` is clear the table by
resigning a new empty one, because this script does not run persistently like
nvim so GC is not triggered, so I added another extreme control group that
manually triggers GC.
This commit is contained in:
Yi Ming
2026-04-22 23:38:58 +08:00
committed by GitHub
parent 61fb88992d
commit 558204d87b
5 changed files with 53 additions and 23 deletions

View File

@@ -1,5 +1,6 @@
local util = require('vim.lsp.util')
local log = require('vim.lsp.log')
local tableclear = require('vim._core.table').clear
local api = vim.api
local M = {}
@@ -99,10 +100,10 @@ end
function Provider:clear()
self:reset_timer()
self.version = nil
self.row_version = {}
tableclear(self.row_version)
for _, state in pairs(self.client_state) do
state.row_lenses = {}
tableclear(state.row_lenses)
api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1)
end
@@ -130,8 +131,8 @@ function Provider:handler(err, result, ctx)
return
end
---@type table<integer, lsp.CodeLens[]>
local row_lenses = {}
local row_lenses = state.row_lenses
tableclear(row_lenses)
-- Code lenses should only span a single line.
for _, lens in ipairs(result or {}) do
@@ -140,8 +141,6 @@ function Provider:handler(err, result, ctx)
table.insert(lenses, lens)
row_lenses[row] = lenses
end
state.row_lenses = row_lenses
self.version = ctx.version
api.nvim__redraw({ buf = self.bufnr, valid = true, flush = false })
@@ -516,7 +515,7 @@ function M.on_refresh(err, _, ctx)
if not provider.timer then
provider:request(client_id, function()
if api.nvim_buf_is_valid(bufnr) then
provider.row_version = {}
tableclear(provider.row_version)
vim.api.nvim__redraw({ buf = bufnr, valid = true, flush = false })
end
end)