mirror of https://github.com/folke/lazy.nvim.git
perf: merge module/cache and use ffi to pack cache data
This commit is contained in:
parent
4438faf9a9
commit
e1c08d64b3
|
@ -1,96 +0,0 @@
|
||||||
-- Simple string cache with fast saving and loading from file
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
M.dirty = false
|
|
||||||
|
|
||||||
local cache_path = vim.fn.stdpath("state") .. "/lazy.state"
|
|
||||||
---@type string
|
|
||||||
local cache_hash = ""
|
|
||||||
---@type table<string,boolean>
|
|
||||||
local used = {}
|
|
||||||
---@type table<string,string>
|
|
||||||
local cache = {}
|
|
||||||
|
|
||||||
---@return string?
|
|
||||||
function M.get(key)
|
|
||||||
if cache[key] then
|
|
||||||
used[key] = true
|
|
||||||
return cache[key]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.debug()
|
|
||||||
local ret = {}
|
|
||||||
for key, value in pairs(cache) do
|
|
||||||
ret[key] = #value
|
|
||||||
end
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.set(key, value)
|
|
||||||
cache[key] = value
|
|
||||||
used[key] = true
|
|
||||||
M.dirty = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.del(key)
|
|
||||||
cache[key] = nil
|
|
||||||
M.dirty = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.hash(file)
|
|
||||||
local stat = vim.loop.fs_stat(file)
|
|
||||||
return stat and (stat.mtime.sec .. stat.mtime.nsec .. stat.size)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.setup()
|
|
||||||
cache = {}
|
|
||||||
local f = io.open(cache_path, "rb")
|
|
||||||
if f then
|
|
||||||
cache_hash = M.hash(cache_path)
|
|
||||||
---@type string
|
|
||||||
local data = f:read("*a")
|
|
||||||
f:close()
|
|
||||||
|
|
||||||
local from = 1
|
|
||||||
local to = data:find("\0", from, true)
|
|
||||||
while to do
|
|
||||||
local key = data:sub(from, to - 1)
|
|
||||||
from = to + 1
|
|
||||||
to = data:find("\0", from, true)
|
|
||||||
local len = tonumber(data:sub(from, to - 1))
|
|
||||||
from = to + 1
|
|
||||||
cache[key] = data:sub(from, from + len - 1)
|
|
||||||
from = from + len
|
|
||||||
to = data:find("\0", from, true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.autosave()
|
|
||||||
vim.api.nvim_create_autocmd("VimLeavePre", {
|
|
||||||
callback = function()
|
|
||||||
if M.dirty then
|
|
||||||
local hash = M.hash(cache_path)
|
|
||||||
-- abort when the file was changed in the meantime
|
|
||||||
if hash == nil or cache_hash == hash then
|
|
||||||
M.save()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.save()
|
|
||||||
require("lazy.core.module").save()
|
|
||||||
|
|
||||||
local f = assert(io.open(cache_path, "wb"))
|
|
||||||
for key, value in pairs(cache) do
|
|
||||||
if used[key] then
|
|
||||||
f:write(key, "\0", tostring(#value), "\0", value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
|
@ -68,7 +68,7 @@ function M.setup(opts)
|
||||||
pattern = "VeryLazy",
|
pattern = "VeryLazy",
|
||||||
once = true,
|
once = true,
|
||||||
callback = function()
|
callback = function()
|
||||||
require("lazy.core.cache").autosave()
|
require("lazy.core.module").autosave()
|
||||||
require("lazy.view").setup()
|
require("lazy.view").setup()
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,51 +1,50 @@
|
||||||
local Cache = require("lazy.core.cache")
|
local ffi = require("ffi")
|
||||||
|
---@diagnostic disable-next-line: no-unknown
|
||||||
|
local uv = vim.loop
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
M.dirty = false
|
||||||
|
|
||||||
---@type table<string, string>
|
local cache_path = vim.fn.stdpath("state") .. "/lazy.state"
|
||||||
M.hashes = {}
|
---@type CacheHash
|
||||||
|
local cache_hash
|
||||||
|
|
||||||
|
---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number}
|
||||||
|
---@alias CacheEntry {hash:CacheHash, chunk:string, used:boolean}
|
||||||
|
---@type table<string,CacheEntry?>
|
||||||
|
M.cache = {}
|
||||||
|
|
||||||
---@param modname string
|
---@param modname string
|
||||||
---@param modpath string
|
---@param modpath string
|
||||||
---@return any
|
---@return any
|
||||||
function M.load(modname, modpath)
|
function M.load(modname, modpath)
|
||||||
local err
|
local entry = M.cache[modname]
|
||||||
---@type (string|fun())?
|
local hash = assert(M.hash(modpath))
|
||||||
local chunk = Cache.get(modname)
|
|
||||||
|
|
||||||
local hash = Cache.hash(modpath)
|
if entry and not M.eq(entry.hash, hash) then
|
||||||
if hash ~= M.hashes[modname] then
|
entry = nil
|
||||||
M.hashes[modname] = hash
|
|
||||||
Cache.del(modname)
|
|
||||||
chunk = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if chunk then
|
local chunk, err
|
||||||
chunk, err = load(chunk --[[@as string]], "@" .. modpath, "b")
|
if entry then
|
||||||
|
entry.used = true
|
||||||
|
chunk, err = load(entry.chunk --[[@as string]], "@" .. modpath, "b")
|
||||||
else
|
else
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
vim.notify("loadfile(" .. modname .. ")")
|
vim.notify("loadfile(" .. modname .. ")")
|
||||||
end)
|
end)
|
||||||
chunk, err = loadfile(modpath)
|
chunk, err = loadfile(modpath)
|
||||||
if chunk and not err then
|
if chunk then
|
||||||
Cache.set(modname, string.dump(chunk))
|
M.dirty = true
|
||||||
|
M.cache[modname] = { hash = hash, chunk = string.dump(chunk), used = true }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if chunk then
|
return chunk and chunk() or error(err)
|
||||||
return chunk()
|
|
||||||
else
|
|
||||||
error(err)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup()
|
function M.setup()
|
||||||
-- load cache
|
M.load_cache()
|
||||||
local value = Cache.get("cache.modules")
|
|
||||||
if value then
|
|
||||||
M.hashes = vim.json.decode(value)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- preload core modules
|
-- preload core modules
|
||||||
local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p:h:h")
|
local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p:h:h")
|
||||||
for _, name in ipairs({ "util", "config", "loader", "plugin", "handler" }) do
|
for _, name in ipairs({ "util", "config", "loader", "plugin", "handler" }) do
|
||||||
|
@ -58,8 +57,67 @@ function M.setup()
|
||||||
return M
|
return M
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.save()
|
---@return CacheHash?
|
||||||
Cache.set("cache.modules", vim.json.encode(M.hashes))
|
function M.hash(file)
|
||||||
|
return uv.fs_stat(file)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param h1 CacheHash
|
||||||
|
---@param h2 CacheHash
|
||||||
|
function M.eq(h1, h2)
|
||||||
|
return h1 and h2 and h1.size == h2.size and h1.mtime.sec == h2.mtime.sec and h1.mtime.nsec == h2.mtime.nsec
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.save_cache()
|
||||||
|
local f = assert(uv.fs_open(cache_path, "w", 438))
|
||||||
|
vim.loop.fs_ftruncate(f, 0)
|
||||||
|
for modname, entry in pairs(M.cache) do
|
||||||
|
if entry.used then
|
||||||
|
entry.modname = modname
|
||||||
|
local header = { entry.hash.size, entry.hash.mtime.sec, entry.hash.mtime.nsec, #modname, #entry.chunk }
|
||||||
|
uv.fs_write(f, ffi.string(ffi.new("const uint32_t[5]", header), 20))
|
||||||
|
uv.fs_write(f, modname)
|
||||||
|
uv.fs_write(f, entry.chunk)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
uv.fs_close(f)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.load_cache()
|
||||||
|
M.cache = {}
|
||||||
|
local f = uv.fs_open(cache_path, "r", 438)
|
||||||
|
if f then
|
||||||
|
cache_hash = uv.fs_fstat(f) --[[@as CacheHash]]
|
||||||
|
local data = uv.fs_read(f, cache_hash.size, 0) --[[@as string]]
|
||||||
|
uv.fs_close(f)
|
||||||
|
|
||||||
|
local offset = 1
|
||||||
|
while offset + 1 < #data do
|
||||||
|
local header = ffi.cast("uint32_t*", ffi.new("const char[20]", data:sub(offset, offset + 19)))
|
||||||
|
offset = offset + 20
|
||||||
|
local modname = data:sub(offset, offset + header[3] - 1)
|
||||||
|
offset = offset + header[3]
|
||||||
|
local chunk = data:sub(offset, offset + header[4] - 1)
|
||||||
|
offset = offset + header[4]
|
||||||
|
M.cache[modname] = { hash = { size = header[0], mtime = { sec = header[1], nsec = header[2] } }, chunk = chunk }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.autosave()
|
||||||
|
vim.api.nvim_create_autocmd("VimLeavePre", {
|
||||||
|
callback = function()
|
||||||
|
if M.dirty then
|
||||||
|
local hash = M.hash(cache_path)
|
||||||
|
-- abort when the file was changed in the meantime
|
||||||
|
if hash == nil or M.eq(cache_hash, hash) then
|
||||||
|
vim.fn.system("echo start >> foo.txt")
|
||||||
|
M.save_cache()
|
||||||
|
vim.fn.system("echo stop >> foo.txt")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
|
@ -2,9 +2,6 @@ local M = {}
|
||||||
|
|
||||||
---@param opts? LazyConfig
|
---@param opts? LazyConfig
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
local cache_start = vim.loop.hrtime()
|
|
||||||
require("lazy.core.cache").setup()
|
|
||||||
|
|
||||||
local module_start = vim.loop.hrtime()
|
local module_start = vim.loop.hrtime()
|
||||||
require("lazy.core.module").setup()
|
require("lazy.core.module").setup()
|
||||||
local Util = require("lazy.core.util")
|
local Util = require("lazy.core.util")
|
||||||
|
@ -13,7 +10,6 @@ function M.setup(opts)
|
||||||
local Handler = require("lazy.core.handler")
|
local Handler = require("lazy.core.handler")
|
||||||
local Plugin = require("lazy.core.plugin")
|
local Plugin = require("lazy.core.plugin")
|
||||||
|
|
||||||
Util.track("cache", module_start - cache_start)
|
|
||||||
Util.track("module", vim.loop.hrtime() - module_start)
|
Util.track("module", vim.loop.hrtime() - module_start)
|
||||||
|
|
||||||
Util.track("setup")
|
Util.track("setup")
|
||||||
|
@ -29,10 +25,7 @@ function M.setup(opts)
|
||||||
for _, plugin in pairs(Config.plugins) do
|
for _, plugin in pairs(Config.plugins) do
|
||||||
if not plugin._.installed then
|
if not plugin._.installed then
|
||||||
vim.cmd("do User LazyInstallPre")
|
vim.cmd("do User LazyInstallPre")
|
||||||
require("lazy.manage").install({
|
require("lazy.manage").install({ wait = true, show = Config.options.interactive })
|
||||||
wait = true,
|
|
||||||
show = Config.options.interactive,
|
|
||||||
})
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -43,9 +36,10 @@ function M.setup(opts)
|
||||||
Handler.setup()
|
Handler.setup()
|
||||||
Util.track()
|
Util.track()
|
||||||
|
|
||||||
local lazy_delta = vim.loop.hrtime() - cache_start
|
local lazy_delta = vim.loop.hrtime() - module_start
|
||||||
|
|
||||||
Util.track() -- end setup
|
Util.track() -- end setup
|
||||||
|
|
||||||
Loader.init_plugins()
|
Loader.init_plugins()
|
||||||
|
|
||||||
if Config.plugins["lazy.nvim"] then
|
if Config.plugins["lazy.nvim"] then
|
||||||
|
|
Loading…
Reference in New Issue