Skip to content

Investigate performance of restore_nodes #234

@stasjok

Description

@stasjok

I notices, that restore_nodes are relatively slow when restored (for example when making a choice). For a small number of nodes it is not noticeable, but for large number of nodes switching choices between them is quite slow.

I prepared a snippet set for testing it:

local ls = require("luasnip")
local s = ls.snippet
local sn = ls.snippet_node
local t = ls.text_node
local i = ls.insert_node
local c = ls.choice_node
local d = ls.dynamic_node
local r = ls.restore_node

vim.cmd([[
inoremap <C-L> <Cmd>lua local uv = require("luv"); local time = uv.hrtime(); require('luasnip').change_choice(1); vim.notify(tostring((uv.hrtime() - time)/1000000).."ms")<CR>
nnoremap <C-L> <Cmd>lua local uv = require("luv"); local time = uv.hrtime(); require('luasnip').change_choice(1); vim.notify(tostring((uv.hrtime() - time)/1000000).."ms")<CR>
snoremap <C-L> <Cmd>lua local uv = require("luv"); local time = uv.hrtime(); require('luasnip').change_choice(1); vim.notify(tostring((uv.hrtime() - time)/1000000).."ms")<CR>
]])

local function gen_nodes(count, node_type, start)
  local uv = require("luv")
  local time = uv.hrtime()
  count = count or 50
  start = start or 1
  local nodes = {}
  for j = start, count - start + 1 do
    nodes[j * 3 - 2] = t("Node" .. j .. " = ")
    if not node_type or node_type == 0 then
      nodes[j * 3 - 1] = i(j, "value" .. j)
    elseif node_type == 1 then
      nodes[j * 3 - 1] = r(j, j, i(1, "value" .. j))
    elseif node_type == 2 then
      nodes[j * 3 - 1] = sn(j, { i(1, "value" .. j) })
    else
    end
    nodes[j * 3] = t({ "", "" })
  end
  nodes[#nodes + 1] = t("Generation time: " .. tostring(uv.hrtime() - time) .. "ns")
  return nodes
end

local function gen_dynamic()
  return {
    t("Type (0 - input, 1 - restore, 2 - snippet): "),
    i(1, "1"),
    t(" Number of nodes: "),
    i(2, "20"),
    t({ "", "" }),
    d(3, function(args)
      local type = tonumber(args[1][1])
      local count = tonumber(args[2][1])
      local nodes
      if type and count then
        nodes = gen_nodes(count, type)
      else
        nodes = { t("Error.") }
      end
      return sn(nil, nodes)
    end, { 1, 2 }),
  }
end

ls.snippets = {
  all = {
    s("dynamic_node", {
      c(1, {
        gen_dynamic(),
        gen_dynamic(),
      }),
    }),
    s("dynamic_node_stored", {
      c(1, {
        {
          t({ "Choice 1", "" }),
          r(1, "nodes", sn(1, gen_dynamic())),
        },
        {
          t({ "Choice 2", "" }),
          r(1, "nodes"),
        },
      }),
    }),
    s("dynamic_node_no_choice", gen_dynamic()),
  },
}

for j = 5, 50, 5 do
  for type, name in ipairs({ "inputs", "restores", "snippets" }) do
    ls.snippets.all[#ls.snippets.all + 1] = s(string.format("choice_%s_%d", name, j), {
      c(1, {
        sn(nil, gen_nodes(j, type - 1)),
        sn(nil, gen_nodes(j, type - 1)),
      }),
    })
  end
end

It generates a couple of snippets like choice_input_<i>/choice_restores_<i>/choice_snippets_<i>, where i — number of nodes. After expanding a snippet press <C-L> (or remap to something else) to make a choice. Then with :messages command you can get timings. For my PC after 15-20 restore_nodes timing is not instant (>30 ms). There is no this problem for snippet/input nodes (<10 ms even for 50 nodes). Also there is dynamic_node snippet and stored variant dynamic_node_stored. They allow to dynamically set a number of nodes and test switching speed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions