aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2024-05-03 18:02:25 +0800
committerGitHub <noreply@github.com>2024-05-03 18:02:25 +0800
commitd44ed3a885e163df33cce8180ca9f72fb5c0661a (patch)
tree48c5c793cc69a1bf35c712885bf123e7654e37d7
parentcf9f002f31c8b4d9d42912a3f45f5d3db4462fd9 (diff)
downloadrneovim-d44ed3a885e163df33cce8180ca9f72fb5c0661a.tar.gz
rneovim-d44ed3a885e163df33cce8180ca9f72fb5c0661a.tar.bz2
rneovim-d44ed3a885e163df33cce8180ca9f72fb5c0661a.zip
perf(extmarks): better track whether namespace has extmarks (#28615)
This avoids redraw when adding/removing an empty namespace for a window. This also avoids marktree traversal when clearing a namespace that has already been cleared, which is added as a benchmark.
-rw-r--r--runtime/lua/vim/highlight.lua2
-rw-r--r--src/nvim/api/extmark.c8
-rw-r--r--src/nvim/extmark.c25
-rw-r--r--src/nvim/memfile.c2
-rw-r--r--test/benchmark/extmark_spec.lua45
-rw-r--r--test/functional/api/extmark_spec.lua18
6 files changed, 89 insertions, 11 deletions
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index cdc0f0a0b1..781fabe5fb 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -129,13 +129,13 @@ function M.on_yank(opts)
yank_cancel()
end
+ vim.api.nvim_win_add_ns(winid, yank_ns)
M.range(bufnr, yank_ns, higroup, "'[", "']", {
regtype = event.regtype,
inclusive = event.inclusive,
priority = opts.priority or M.priorities.user,
_scoped = true,
})
- vim.api.nvim_win_add_ns(winid, yank_ns)
yank_cancel = function()
yank_timer = nil
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 60e12e9da8..320e14b654 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -1234,7 +1234,9 @@ Boolean nvim_win_add_ns(Window window, Integer ns_id, Error *err)
set_put(uint32_t, &win->w_ns_set, (uint32_t)ns_id);
- changed_window_setting(win);
+ if (map_has(uint32_t, win->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
+ changed_window_setting(win);
+ }
return true;
}
@@ -1279,7 +1281,9 @@ Boolean nvim_win_remove_ns(Window window, Integer ns_id, Error *err)
set_del(uint32_t, &win->w_ns_set, (uint32_t)ns_id);
- changed_window_setting(win);
+ if (map_has(uint32_t, win->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
+ changed_window_setting(win);
+ }
return true;
}
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index c4a34f8019..3236590010 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -206,7 +206,9 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
}
}
- bool marks_cleared = false;
+ bool marks_cleared_any = false;
+ bool marks_cleared_all = l_row == 0 && l_col == 0;
+
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
@@ -214,16 +216,29 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
if (mark.pos.row < 0
|| mark.pos.row > u_row
|| (mark.pos.row == u_row && mark.pos.col > u_col)) {
+ if (mark.pos.row >= 0) {
+ marks_cleared_all = false;
+ }
break;
}
if (mark.ns == ns_id || all_ns) {
- marks_cleared = true;
+ marks_cleared_any = true;
extmark_del(buf, itr, mark, true);
} else {
marktree_itr_next(buf->b_marktree, itr);
}
}
- return marks_cleared;
+
+ if (marks_cleared_all) {
+ if (all_ns) {
+ map_destroy(uint32_t, buf->b_extmark_ns);
+ *buf->b_extmark_ns = (Map(uint32_t, uint32_t)) MAP_INIT;
+ } else {
+ map_del(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, NULL);
+ }
+ }
+
+ return marks_cleared_any;
}
/// @return the position of marks between a range,
@@ -315,10 +330,6 @@ MTPair extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
/// free extmarks from the buffer
void extmark_free_all(buf_T *buf)
{
- if (!map_size(buf->b_extmark_ns)) {
- return;
- }
-
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, 0, 0, itr);
while (true) {
diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c
index fb9f2eb8df..a1713edb66 100644
--- a/src/nvim/memfile.c
+++ b/src/nvim/memfile.c
@@ -677,7 +677,7 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp)
/// The old number When not found.
blocknr_T mf_trans_del(memfile_T *mfp, blocknr_T old_nr)
{
- blocknr_T *num = map_ref(int64_t, int64_t)(&mfp->mf_trans, old_nr, false);
+ blocknr_T *num = map_ref(int64_t, int64_t)(&mfp->mf_trans, old_nr, NULL);
if (num == NULL) { // not found
return old_nr;
}
diff --git a/test/benchmark/extmark_spec.lua b/test/benchmark/extmark_spec.lua
new file mode 100644
index 0000000000..0d284b363c
--- /dev/null
+++ b/test/benchmark/extmark_spec.lua
@@ -0,0 +1,45 @@
+local n = require('test.functional.testnvim')()
+
+local clear = n.clear
+local exec_lua = n.exec_lua
+
+describe('extmark perf', function()
+ before_each(function()
+ clear()
+
+ exec_lua([[
+ out = {}
+ function start()
+ ts = vim.uv.hrtime()
+ end
+ function stop(name)
+ out[#out+1] = ('%14.6f ms - %s'):format((vim.uv.hrtime() - ts) / 1000000, name)
+ end
+ ]])
+ end)
+
+ after_each(function()
+ for _, line in ipairs(exec_lua([[return out]])) do
+ print(line)
+ end
+ end)
+
+ it('repeatedly calling nvim_buf_clear_namespace #28615', function()
+ exec_lua([[
+ vim.api.nvim_buf_set_lines(0, 0, -1, true, { 'foo', 'bar' })
+ local ns0 = vim.api.nvim_create_namespace('ns0')
+ local ns1 = vim.api.nvim_create_namespace('ns1')
+
+ for _ = 1, 10000 do
+ vim.api.nvim_buf_set_extmark(0, ns0, 0, 0, {})
+ end
+ vim.api.nvim_buf_set_extmark(0, ns1, 1, 0, {})
+
+ start()
+ for _ = 1, 10000 do
+ vim.api.nvim_buf_clear_namespace(0, ns1, 0, -1)
+ end
+ stop('nvim_buf_clear_namespace')
+ ]])
+ end)
+end)
diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua
index 70bdb050e5..965a60417a 100644
--- a/test/functional/api/extmark_spec.lua
+++ b/test/functional/api/extmark_spec.lua
@@ -1896,6 +1896,24 @@ describe('Extmarks buffer api with many marks', function()
end
eq(ns_marks[ns1], get_marks(ns1))
eq(ns_marks[ns2], get_marks(ns2))
+
+ api.nvim_buf_clear_namespace(0, ns1, 0, 10)
+ for id, mark in pairs(ns_marks[ns1]) do
+ if mark[1] < 10 then
+ ns_marks[ns1][id] = nil
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
+
+ api.nvim_buf_clear_namespace(0, ns1, 20, -1)
+ for id, mark in pairs(ns_marks[ns1]) do
+ if mark[1] >= 20 then
+ ns_marks[ns1][id] = nil
+ end
+ end
+ eq(ns_marks[ns1], get_marks(ns1))
+ eq(ns_marks[ns2], get_marks(ns2))
end)
it('can delete line', function()