mirror of
https://github.com/neovim/neovim.git
synced 2026-05-24 05:40:08 +00:00
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:
@@ -168,6 +168,8 @@ PERFORMANCE
|
||||
functions, which skips the Vimscript <=> Lua "bridge" (no data
|
||||
conversion/marshalling) entirely, if the `vim.fn` function is called from
|
||||
Lua.
|
||||
• The table holding LSP data is now cleared using `table.clear`,
|
||||
thus reducing GC and memory reallocation during each data reset.
|
||||
|
||||
PLUGINS
|
||||
|
||||
|
||||
27
runtime/lua/vim/_core/table.lua
Normal file
27
runtime/lua/vim/_core/table.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
-- Basic shim for LuaJIT's table.new and table.clear.
|
||||
local has_new, new = pcall(require, 'table.new')
|
||||
local has_clear, clear = pcall(require, 'table.clear')
|
||||
|
||||
local M = {}
|
||||
|
||||
if not has_new then
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
new = function(narr, nrec)
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
if not has_clear then
|
||||
clear = function(tab)
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
for k in pairs(tab) do
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
tab[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
M.new = new
|
||||
M.clear = clear
|
||||
|
||||
return M
|
||||
@@ -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
|
||||
|
||||
---@type table<lsp.FoldingRangeKind, true>
|
||||
@@ -51,12 +52,13 @@ Capability.all[State.name] = State
|
||||
|
||||
--- Re-evaluate the cached foldinfo in the buffer.
|
||||
function State:evaluate()
|
||||
---@type table<integer, [integer, ">" | "<"?]?>
|
||||
local row_level = {}
|
||||
---@type table<integer, table<lsp.FoldingRangeKind, true?>?>>
|
||||
local row_kinds = {}
|
||||
---@type table<integer, string?>
|
||||
local row_text = {}
|
||||
local row_level, row_kinds, row_text, row_virt_text =
|
||||
self.row_level, self.row_kinds, self.row_text, self.row_virt_text
|
||||
|
||||
tableclear(row_level)
|
||||
tableclear(row_kinds)
|
||||
tableclear(row_text)
|
||||
tableclear(row_virt_text)
|
||||
|
||||
for client_id, ranges in pairs(self.client_state) do
|
||||
for _, range in ipairs(ranges) do
|
||||
@@ -88,11 +90,6 @@ function State:evaluate()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.row_level = row_level
|
||||
self.row_kinds = row_kinds
|
||||
self.row_text = row_text
|
||||
self.row_virt_text = {}
|
||||
end
|
||||
|
||||
--- Force `foldexpr()` to be re-evaluated, without opening folds.
|
||||
@@ -190,10 +187,10 @@ end
|
||||
|
||||
function State:reset()
|
||||
self.lang = vim.treesitter.language.get_lang(vim.bo[self.bufnr].filetype)
|
||||
self.row_level = {}
|
||||
self.row_kinds = {}
|
||||
self.row_text = {}
|
||||
self.row_virt_text = {}
|
||||
tableclear(self.row_level)
|
||||
tableclear(self.row_kinds)
|
||||
tableclear(self.row_text)
|
||||
tableclear(self.row_virt_text)
|
||||
end
|
||||
|
||||
--- Initialize `state` and event hooks, then request folding ranges.
|
||||
@@ -201,7 +198,11 @@ end
|
||||
---@return vim.lsp.folding_range.State
|
||||
function State:new(bufnr)
|
||||
self = Capability.new(self, bufnr)
|
||||
self:reset()
|
||||
self.lang = vim.treesitter.language.get_lang(vim.bo[self.bufnr].filetype)
|
||||
self.row_level = {}
|
||||
self.row_kinds = {}
|
||||
self.row_text = {}
|
||||
self.row_virt_text = {}
|
||||
|
||||
api.nvim_buf_attach(bufnr, false, {
|
||||
-- Reset `bufstate` and request folding ranges.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -233,6 +233,7 @@ describe('vim._core', function()
|
||||
'vim._core.shared',
|
||||
'vim._core.stringbuffer',
|
||||
'vim._core.system',
|
||||
'vim._core.table',
|
||||
'vim._core.ui2',
|
||||
'vim._core.util',
|
||||
'vim._core.vimfn',
|
||||
|
||||
Reference in New Issue
Block a user