Skip to content

Commit 0b6dec4

Browse files
committedDec 2, 2022
perf: module now caches all lua modules used till VimEnter
1 parent 723274e commit 0b6dec4

File tree

1 file changed

+98
-33
lines changed

1 file changed

+98
-33
lines changed
 

‎lua/lazy/core/module.lua

+98-33
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,100 @@ local cache_path = vim.fn.stdpath("state") .. "/lazy.state"
1010
local cache_hash
1111

1212
---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number}
13-
---@alias CacheEntry {hash:CacheHash, chunk:string, used:boolean}
13+
---@alias CacheEntry {hash:CacheHash, modpath:string, chunk:string, used:number}
1414
---@type table<string,CacheEntry?>
1515
M.cache = {}
16+
M.loader_idx = 2 -- 2 so preload still works
17+
M.enabled = true
18+
M.ttl = 3600 * 24 * 5 -- keep unused modules for up to 5 days
1619

20+
-- Check if we need to load this plugin
1721
---@param modname string
1822
---@param modpath string
19-
---@return any
20-
function M.load(modname, modpath)
21-
local entry = M.cache[modname]
22-
local hash = assert(M.hash(modpath))
23+
function M.check_load(modname, modpath)
24+
if modname:sub(1, 4) == "lazy" then
25+
return
26+
end
27+
require("lazy.core.loader").autoload(modname, modpath)
28+
end
2329

24-
if entry and not M.eq(entry.hash, hash) then
25-
entry = nil
30+
---@param modname string
31+
---@return any
32+
function M.loader(modname)
33+
if not M.enabled then
34+
return "lazy loader is disabled"
2635
end
2736

37+
local entry = M.cache[modname]
38+
2839
local chunk, err
2940
if entry then
30-
entry.used = true
31-
chunk, err = load(entry.chunk --[[@as string]], "@" .. modpath, "b")
41+
M.check_load(modname, entry.modpath)
42+
entry.used = os.time()
43+
local hash = assert(M.hash(entry.modpath))
44+
if M.eq(entry.hash, hash) then
45+
-- found in cache and up to date
46+
chunk, err = load(entry.chunk --[[@as string]], "@" .. entry.modpath)
47+
return chunk or error(err)
48+
end
49+
-- reload from file
50+
entry.hash = hash
51+
chunk, err = loadfile(entry.modpath)
3252
else
33-
vim.schedule(function()
34-
vim.notify("loadfile(" .. modname .. ")")
35-
end)
36-
chunk, err = loadfile(modpath)
37-
if chunk then
38-
M.dirty = true
39-
M.cache[modname] = { hash = hash, chunk = string.dump(chunk), used = true }
53+
-- load the module and find its modpath
54+
local modpath
55+
chunk, modpath = M.find(modname)
56+
if modpath then
57+
entry = { hash = M.hash(modpath), modpath = modpath, used = os.time() }
58+
M.cache[modname] = entry
4059
end
4160
end
42-
43-
return chunk and chunk() or error(err)
61+
vim.schedule(function()
62+
vim.notify("loading " .. modname)
63+
end)
64+
if entry and chunk then
65+
M.dirty = true
66+
entry.chunk = string.dump(chunk)
67+
end
68+
return chunk or error(err)
4469
end
4570

46-
function M.setup()
47-
M.load_cache()
48-
-- preload core modules
49-
local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p:h:h")
50-
for _, name in ipairs({ "util", "config", "loader", "plugin", "handler" }) do
51-
local modname = "lazy.core." .. name
71+
---@param modname string
72+
function M.find(modname)
73+
-- update our loader position if needed
74+
if package.loaders[M.loader_idx] ~= M.loader then
75+
M.loader_idx = 1
5276
---@diagnostic disable-next-line: no-unknown
53-
package.preload[modname] = function()
54-
return M.load(modname, root .. "/core/" .. name:gsub("%.", "/") .. ".lua")
77+
for i, loader in ipairs(package.loaders) do
78+
if loader == M.loader then
79+
M.loader_idx = i
80+
break
81+
end
5582
end
5683
end
57-
return M
84+
85+
-- find the module and its modpath
86+
for i = M.loader_idx + 1, #package.loaders do
87+
---@diagnostic disable-next-line: no-unknown
88+
local chunk = package.loaders[i](modname)
89+
if type(chunk) == "function" then
90+
local info = debug.getinfo(chunk, "S")
91+
return chunk, (info.what ~= "C" and info.source:sub(2))
92+
end
93+
end
94+
end
95+
96+
function M.setup()
97+
M.load_cache()
98+
table.insert(package.loaders, M.loader_idx, M.loader)
99+
100+
vim.api.nvim_create_autocmd("VimEnter", {
101+
once = true,
102+
callback = function()
103+
-- startup done, so stop caching
104+
M.enabled = false
105+
end,
106+
})
58107
end
59108

60109
---@return CacheHash?
@@ -71,12 +120,21 @@ end
71120
function M.save_cache()
72121
local f = assert(uv.fs_open(cache_path, "w", 438))
73122
for modname, entry in pairs(M.cache) do
74-
if entry.used then
123+
if entry.used > os.time() - M.ttl then
75124
entry.modname = modname
76-
local header = { entry.hash.size, entry.hash.mtime.sec, entry.hash.mtime.nsec, #modname, #entry.chunk }
77-
uv.fs_write(f, ffi.string(ffi.new("const uint32_t[5]", header), 20))
125+
local header = {
126+
entry.hash.size,
127+
entry.hash.mtime.sec,
128+
entry.hash.mtime.nsec,
129+
#modname,
130+
#entry.chunk,
131+
#entry.modpath,
132+
entry.used,
133+
}
134+
uv.fs_write(f, ffi.string(ffi.new("const uint32_t[7]", header), 28))
78135
uv.fs_write(f, modname)
79136
uv.fs_write(f, entry.chunk)
137+
uv.fs_write(f, entry.modpath)
80138
end
81139
end
82140
uv.fs_close(f)
@@ -92,13 +150,20 @@ function M.load_cache()
92150

93151
local offset = 1
94152
while offset + 1 < #data do
95-
local header = ffi.cast("uint32_t*", ffi.new("const char[20]", data:sub(offset, offset + 19)))
96-
offset = offset + 20
153+
local header = ffi.cast("uint32_t*", ffi.new("const char[28]", data:sub(offset, offset + 27)))
154+
offset = offset + 28
97155
local modname = data:sub(offset, offset + header[3] - 1)
98156
offset = offset + header[3]
99157
local chunk = data:sub(offset, offset + header[4] - 1)
100158
offset = offset + header[4]
101-
M.cache[modname] = { hash = { size = header[0], mtime = { sec = header[1], nsec = header[2] } }, chunk = chunk }
159+
local file = data:sub(offset, offset + header[5] - 1)
160+
offset = offset + header[5]
161+
M.cache[modname] = {
162+
hash = { size = header[0], mtime = { sec = header[1], nsec = header[2] } },
163+
chunk = chunk,
164+
modpath = file,
165+
used = header[6],
166+
}
102167
end
103168
end
104169
end

0 commit comments

Comments
 (0)
Please sign in to comment.