Skip to content

Commit 2507fd5

Browse files
committed
perf: tons of performance improvements. Lazy should now load in about 1.5ms for 97 plugins
1 parent 711834f commit 2507fd5

File tree

11 files changed

+214
-272
lines changed

11 files changed

+214
-272
lines changed

lua/lazy/core/cache.lua

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ local used = {}
1212
---@type table<string,string>
1313
local cache = {}
1414

15+
---@return string?
1516
function M.get(key)
1617
if cache[key] then
1718
used[key] = true

lua/lazy/core/config.lua

+13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ M.defaults = {
88
plugins = "config.plugins",
99
plugins_local = {
1010
path = vim.fn.expand("~/projects"),
11+
---@type string[]
1112
patterns = {},
1213
},
1314
package_path = vim.fn.stdpath("data") .. "/site/pack/lazy",
@@ -27,15 +28,27 @@ M.defaults = {
2728

2829
M.ns = vim.api.nvim_create_namespace("lazy")
2930

31+
M.paths = {
32+
---@type string
33+
main = nil,
34+
---@type string
35+
plugins = nil,
36+
}
37+
3038
---@type table<string, LazyPlugin>
3139
M.plugins = {}
3240

41+
---@type LazyPlugin[]
42+
M.to_clean = {}
43+
3344
---@type LazyConfig
3445
M.options = {}
3546

3647
---@param opts? LazyConfig
3748
function M.setup(opts)
3849
M.options = vim.tbl_deep_extend("force", M.defaults, opts or {})
50+
M.paths.plugins = vim.fn.stdpath("config") .. "/lua/" .. M.options.plugins:gsub("%.", "/")
51+
M.paths.main = M.paths.plugins .. (vim.loop.fs_stat(M.paths.plugins .. ".lua") and ".lua" or "/init.lua")
3952

4053
-- vim.fn.mkdir(M.options.package_path, "p")
4154

lua/lazy/core/loader.lua

+13-14
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ M.loading = {}
2020

2121
---@param plugin LazyPlugin
2222
function M.add(plugin)
23-
if plugin.init or (plugin.opt == false and plugin.config) then
23+
if plugin.init or (plugin.opt == false) then
2424
table.insert(M.loaders.init, plugin.name)
2525
end
2626

2727
for _, loader_type in ipairs(M.types) do
28+
---@type (string|string[])?
2829
local loaders = plugin[loader_type]
2930
if plugin[loader_type] then
3031
loaders = type(loaders) == "table" and loaders or { loaders }
@@ -198,7 +199,8 @@ end
198199

199200
---@param plugins string|LazyPlugin|string[]|LazyPlugin[]
200201
---@param reason {[string]:string}
201-
function M.load(plugins, reason)
202+
---@param opts? {load_start: boolean}
203+
function M.load(plugins, reason, opts)
202204
if type(plugins) == "string" or plugins.name then
203205
---@diagnostic disable-next-line: assign-type-mismatch
204206
plugins = { plugins }
@@ -211,6 +213,7 @@ function M.load(plugins, reason)
211213
end
212214

213215
if not plugin.loaded then
216+
---@diagnostic disable-next-line: assign-type-mismatch
214217
plugin.loaded = {}
215218
for k, v in pairs(reason) do
216219
plugin.loaded[k] = v
@@ -222,7 +225,7 @@ function M.load(plugins, reason)
222225
table.insert(M.loading, plugin)
223226

224227
Util.track(plugin.name)
225-
M.packadd(plugin)
228+
M.packadd(plugin, opts and opts.load_start)
226229

227230
if plugin.requires then
228231
M.load(plugin.requires, {})
@@ -242,11 +245,11 @@ function M.load(plugins, reason)
242245
end
243246

244247
---@param plugin LazyPlugin
245-
function M.packadd(plugin)
248+
function M.packadd(plugin, load_start)
246249
if plugin.opt then
247250
vim.cmd.packadd(plugin.pack)
248251
M.source_plugin_files(plugin, true)
249-
else
252+
elseif load_start then
250253
vim.opt.runtimepath:append(plugin.dir)
251254
M.source_plugin_files(plugin)
252255
M.source_plugin_files(plugin, true)
@@ -256,16 +259,12 @@ end
256259
---@param plugin LazyPlugin
257260
---@param after? boolean
258261
function M.source_plugin_files(plugin, after)
259-
local pattern = (after and "/after" or "") .. ("/plugin/" .. "**/*.\\(vim\\|lua\\)")
260-
261-
local _, entries = pcall(vim.fn.glob, plugin.dir .. "/" .. pattern, false, true)
262-
263-
if entries then
264-
---@cast entries string[]
265-
for _, file in ipairs(entries) do
266-
vim.cmd("silent source " .. file)
262+
Util.walk(plugin.dir .. (after and "/after" or "") .. "/plugin", function(path, _, t)
263+
local ext = path:sub(-3)
264+
if t == "file" and (ext == "lua" or ext == "vim") then
265+
vim.cmd("silent source " .. path)
267266
end
268-
end
267+
end)
269268
end
270269

271270
return M

lua/lazy/core/module.lua

+38-98
Original file line numberDiff line numberDiff line change
@@ -2,132 +2,72 @@ local Cache = require("lazy.core.cache")
22

33
local M = {}
44

5-
---@type table<string, {file: string, hash?:string}>
6-
M.modules = {}
5+
---@type table<string, string>
6+
M.hashes = {}
77

8-
function M.add(modname, file)
9-
if not M.modules[modname] then
10-
M.modules[modname] = { file = file }
11-
end
8+
function M.is_dirty(modname, modpath)
9+
return not (Cache.get(modname) and M.hashes[modname] and M.hashes[modname] == Cache.hash(modpath))
1210
end
1311

1412
---@param modname string
15-
function M.load(modname)
16-
if type(package.loaded[modname]) == "table" then
17-
return package.loaded[modname]
18-
end
19-
20-
local info = M.modules[modname]
21-
if info then
22-
local err
23-
---@type string|fun()|nil
24-
local chunk = Cache.get(modname)
25-
26-
if not chunk then
27-
vim.schedule(function()
28-
vim.notify("loading " .. modname)
29-
end)
30-
chunk, err = loadfile(info.file)
31-
if chunk then
32-
Cache.set(modname, string.dump(chunk))
33-
info.hash = info.hash or Cache.hash(info.file)
34-
end
13+
---@param modpath string
14+
---@return table
15+
function M.load(modname, modpath)
16+
local err
17+
---@type (string|fun())?
18+
local chunk = Cache.get(modname)
19+
20+
if chunk then
21+
local hash = Cache.hash(modpath)
22+
if hash ~= M.hashes[modname] then
23+
M.hashes[modname] = hash
24+
chunk = nil
3525
end
36-
37-
if type(chunk) == "string" then
38-
chunk, err = loadstring(chunk --[[@as string]], "@" .. info.file)
39-
end
40-
41-
if not chunk then
42-
error(err)
43-
end
44-
45-
---@type table
46-
local mod = chunk()
47-
package.loaded[modname] = mod
48-
return mod
4926
end
50-
end
5127

52-
local function _add_module(dir, modname)
53-
local d = vim.loop.fs_opendir(dir, nil, 100)
54-
if d then
55-
---@type {name: string, type: "file"|"directory"|"link"}[]
56-
local entries = vim.loop.fs_readdir(d)
57-
while entries do
58-
for _, entry in ipairs(entries) do
59-
local path = dir .. "/" .. entry.name
60-
if entry.type == "directory" then
61-
_add_module(path, modname and (modname .. "." .. entry.name) or entry.name)
62-
else
63-
local childname = entry.name:match("^(.*)%.lua$")
64-
if childname then
65-
local child = entry.name == "init.lua" and modname or modname and (modname .. "." .. childname) or childname
66-
if child then
67-
M.add(child, path)
68-
end
69-
end
70-
end
71-
end
72-
entries = vim.loop.fs_readdir(d)
28+
if chunk then
29+
chunk, err = loadstring(chunk --[[@as string]], "@" .. modpath)
30+
else
31+
vim.schedule(function()
32+
vim.notify("loadfile(" .. modname .. ")")
33+
end)
34+
chunk, err = loadfile(modpath)
35+
if chunk then
36+
Cache.set(modname, string.dump(chunk))
37+
M.hashes[modname] = M.hashes[modname] or Cache.hash(modpath)
7338
end
74-
vim.loop.fs_closedir(d)
7539
end
76-
end
7740

78-
function M.add_module(path)
79-
if path:find("/lua/?$") then
80-
return _add_module(path)
81-
end
82-
---@type string
83-
local modname = path:match("/lua/(.*)/?")
84-
assert(modname)
85-
modname = modname:gsub("/", ".")
86-
if vim.loop.fs_stat(path .. ".lua") then
87-
M.add(modname, path .. ".lua")
41+
if chunk then
42+
---@diagnostic disable-next-line: no-unknown
43+
package.loaded[modname] = chunk()
44+
return package.loaded[modname]
45+
else
46+
error(err)
8847
end
89-
_add_module(path, modname)
9048
end
9149

9250
function M.setup()
9351
-- load cache
9452
local value = Cache.get("cache.modules")
9553
if value then
96-
M.modules = vim.json.decode(value)
97-
for k, v in pairs(M.modules) do
98-
if Cache.hash(v.file) ~= v.hash then
99-
Cache.del(k)
100-
M.changed = true
101-
M.modules[k] = nil
102-
end
103-
end
54+
M.hashes = vim.json.decode(value)
10455
end
10556

10657
-- preload core modules
10758
local root = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":p:h:h")
10859
for _, name in ipairs({ "util", "config", "loader", "state" }) do
10960
local modname = "lazy.core." .. name
110-
M.add(modname, root .. "/core/" .. name:gsub("%.", "/") .. ".lua")
111-
end
112-
113-
table.insert(package.loaders, 2, function(modname)
114-
if M.modules[modname] then
115-
return function()
116-
return M.load(modname)
117-
end
61+
---@diagnostic disable-next-line: no-unknown
62+
package.preload[modname] = function()
63+
return M.load(modname, root .. "/core/" .. name:gsub("%.", "/") .. ".lua")
11864
end
119-
end)
65+
end
12066
return M
12167
end
12268

12369
function M.save()
124-
local value = {}
125-
for k, v in pairs(M.modules) do
126-
if v.hash then
127-
value[k] = v
128-
end
129-
end
130-
Cache.set("cache.modules", vim.json.encode(value))
70+
Cache.set("cache.modules", vim.json.encode(M.hashes))
13171
end
13272

13373
return M

0 commit comments

Comments
 (0)