perf: module now caches all lua modules used till VimEnter

This commit is contained in:
Folke Lemaitre 2022-12-02 09:20:48 +01:00
parent 723274efee
commit 0b6dec46e0
No known key found for this signature in database
GPG Key ID: 41F8B1FBACAE2040
1 changed files with 99 additions and 34 deletions

View File

@ -10,51 +10,100 @@ local cache_path = vim.fn.stdpath("state") .. "/lazy.state"
local cache_hash local cache_hash
---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number} ---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number}
---@alias CacheEntry {hash:CacheHash, chunk:string, used:boolean} ---@alias CacheEntry {hash:CacheHash, modpath:string, chunk:string, used:number}
---@type table<string,CacheEntry?> ---@type table<string,CacheEntry?>
M.cache = {} M.cache = {}
M.loader_idx = 2 -- 2 so preload still works
M.enabled = true
M.ttl = 3600 * 24 * 5 -- keep unused modules for up to 5 days
-- Check if we need to load this plugin
---@param modname string ---@param modname string
---@param modpath string ---@param modpath string
---@return any function M.check_load(modname, modpath)
function M.load(modname, modpath) if modname:sub(1, 4) == "lazy" then
local entry = M.cache[modname] return
local hash = assert(M.hash(modpath))
if entry and not M.eq(entry.hash, hash) then
entry = nil
end end
require("lazy.core.loader").autoload(modname, modpath)
end
---@param modname string
---@return any
function M.loader(modname)
if not M.enabled then
return "lazy loader is disabled"
end
local entry = M.cache[modname]
local chunk, err local chunk, err
if entry then if entry then
entry.used = true M.check_load(modname, entry.modpath)
chunk, err = load(entry.chunk --[[@as string]], "@" .. modpath, "b") entry.used = os.time()
local hash = assert(M.hash(entry.modpath))
if M.eq(entry.hash, hash) then
-- found in cache and up to date
chunk, err = load(entry.chunk --[[@as string]], "@" .. entry.modpath)
return chunk or error(err)
end
-- reload from file
entry.hash = hash
chunk, err = loadfile(entry.modpath)
else else
vim.schedule(function() -- load the module and find its modpath
vim.notify("loadfile(" .. modname .. ")") local modpath
end) chunk, modpath = M.find(modname)
chunk, err = loadfile(modpath) if modpath then
if chunk then entry = { hash = M.hash(modpath), modpath = modpath, used = os.time() }
M.dirty = true M.cache[modname] = entry
M.cache[modname] = { hash = hash, chunk = string.dump(chunk), used = true } end
end
vim.schedule(function()
vim.notify("loading " .. modname)
end)
if entry and chunk then
M.dirty = true
entry.chunk = string.dump(chunk)
end
return chunk or error(err)
end
---@param modname string
function M.find(modname)
-- update our loader position if needed
if package.loaders[M.loader_idx] ~= M.loader then
M.loader_idx = 1
---@diagnostic disable-next-line: no-unknown
for i, loader in ipairs(package.loaders) do
if loader == M.loader then
M.loader_idx = i
break
end
end end
end end
return chunk and chunk() or error(err) -- find the module and its modpath
for i = M.loader_idx + 1, #package.loaders do
---@diagnostic disable-next-line: no-unknown
local chunk = package.loaders[i](modname)
if type(chunk) == "function" then
local info = debug.getinfo(chunk, "S")
return chunk, (info.what ~= "C" and info.source:sub(2))
end
end
end end
function M.setup() function M.setup()
M.load_cache() M.load_cache()
-- preload core modules table.insert(package.loaders, M.loader_idx, M.loader)
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 vim.api.nvim_create_autocmd("VimEnter", {
local modname = "lazy.core." .. name once = true,
---@diagnostic disable-next-line: no-unknown callback = function()
package.preload[modname] = function() -- startup done, so stop caching
return M.load(modname, root .. "/core/" .. name:gsub("%.", "/") .. ".lua") M.enabled = false
end end,
end })
return M
end end
---@return CacheHash? ---@return CacheHash?
@ -71,12 +120,21 @@ end
function M.save_cache() function M.save_cache()
local f = assert(uv.fs_open(cache_path, "w", 438)) local f = assert(uv.fs_open(cache_path, "w", 438))
for modname, entry in pairs(M.cache) do for modname, entry in pairs(M.cache) do
if entry.used then if entry.used > os.time() - M.ttl then
entry.modname = modname entry.modname = modname
local header = { entry.hash.size, entry.hash.mtime.sec, entry.hash.mtime.nsec, #modname, #entry.chunk } local header = {
uv.fs_write(f, ffi.string(ffi.new("const uint32_t[5]", header), 20)) entry.hash.size,
entry.hash.mtime.sec,
entry.hash.mtime.nsec,
#modname,
#entry.chunk,
#entry.modpath,
entry.used,
}
uv.fs_write(f, ffi.string(ffi.new("const uint32_t[7]", header), 28))
uv.fs_write(f, modname) uv.fs_write(f, modname)
uv.fs_write(f, entry.chunk) uv.fs_write(f, entry.chunk)
uv.fs_write(f, entry.modpath)
end end
end end
uv.fs_close(f) uv.fs_close(f)
@ -92,13 +150,20 @@ function M.load_cache()
local offset = 1 local offset = 1
while offset + 1 < #data do while offset + 1 < #data do
local header = ffi.cast("uint32_t*", ffi.new("const char[20]", data:sub(offset, offset + 19))) local header = ffi.cast("uint32_t*", ffi.new("const char[28]", data:sub(offset, offset + 27)))
offset = offset + 20 offset = offset + 28
local modname = data:sub(offset, offset + header[3] - 1) local modname = data:sub(offset, offset + header[3] - 1)
offset = offset + header[3] offset = offset + header[3]
local chunk = data:sub(offset, offset + header[4] - 1) local chunk = data:sub(offset, offset + header[4] - 1)
offset = offset + header[4] offset = offset + header[4]
M.cache[modname] = { hash = { size = header[0], mtime = { sec = header[1], nsec = header[2] } }, chunk = chunk } local file = data:sub(offset, offset + header[5] - 1)
offset = offset + header[5]
M.cache[modname] = {
hash = { size = header[0], mtime = { sec = header[1], nsec = header[2] } },
chunk = chunk,
modpath = file,
used = header[6],
}
end end
end end
end end