perf(loader): use a quicker version of vim.fs.normalize

Problem:

vim.fs.normalize() normalizes too much vim.loader and is slow.

Solution:

Make it faster by doing less. This reduces the times spent in
vim.fs.normalize in vim.loader from ~13ms -> 1-2ms.

Numbers from a relative benchmark:
- Skipping `vim.validate()`: 285ms -> 230ms
- Skipping `path_resolve_dot()`: 285ms -> 60ms
- Skipping `double_slash`: 60ms -> 35ms
This commit is contained in:
Lewis Russell 2024-05-14 17:29:28 +01:00 committed by Lewis Russell
parent 87a45ad9b9
commit dcdefd0428
2 changed files with 27 additions and 12 deletions

View File

@ -488,6 +488,8 @@ end
--- (default: `true`) --- (default: `true`)
--- @field expand_env? boolean --- @field expand_env? boolean
--- ---
--- @field package _fast? boolean
---
--- Path is a Windows path. --- Path is a Windows path.
--- (default: `true` in Windows, `false` otherwise) --- (default: `true` in Windows, `false` otherwise)
--- @field win? boolean --- @field win? boolean
@ -527,11 +529,13 @@ end
function M.normalize(path, opts) function M.normalize(path, opts)
opts = opts or {} opts = opts or {}
vim.validate({ if not opts._fast then
path = { path, { 'string' } }, vim.validate({
expand_env = { opts.expand_env, { 'boolean' }, true }, path = { path, { 'string' } },
win = { opts.win, { 'boolean' }, true }, expand_env = { opts.expand_env, { 'boolean' }, true },
}) win = { opts.win, { 'boolean' }, true },
})
end
local win = opts.win == nil and iswin or not not opts.win local win = opts.win == nil and iswin or not not opts.win
local os_sep_local = win and '\\' or '/' local os_sep_local = win and '\\' or '/'
@ -555,11 +559,17 @@ function M.normalize(path, opts)
path = path:gsub('%$([%w_]+)', vim.uv.os_getenv) path = path:gsub('%$([%w_]+)', vim.uv.os_getenv)
end end
-- Convert path separator to `/` if win then
path = path:gsub(os_sep_local, '/') -- Convert path separator to `/`
path = path:gsub(os_sep_local, '/')
end
-- Check for double slashes at the start of the path because they have special meaning -- Check for double slashes at the start of the path because they have special meaning
local double_slash = vim.startswith(path, '//') and not vim.startswith(path, '///') local double_slash = false
if not opts._fast then
double_slash = vim.startswith(path, '//') and not vim.startswith(path, '///')
end
local prefix = '' local prefix = ''
if win then if win then
@ -576,10 +586,15 @@ function M.normalize(path, opts)
prefix = prefix:gsub('/+', '/') prefix = prefix:gsub('/+', '/')
end end
-- Resolve `.` and `..` components and remove extraneous slashes from path, then recombine prefix if not opts._fast then
-- and path. Preserve leading double slashes as they indicate UNC paths and DOS device paths in -- Resolve `.` and `..` components and remove extraneous slashes from path, then recombine prefix
-- and path.
path = path_resolve_dot(path)
end
-- Preserve leading double slashes as they indicate UNC paths and DOS device paths in
-- Windows and have implementation-defined behavior in POSIX. -- Windows and have implementation-defined behavior in POSIX.
path = (double_slash and '/' or '') .. prefix .. path_resolve_dot(path) path = (double_slash and '/' or '') .. prefix .. path
-- Change empty path to `.` -- Change empty path to `.`
if path == '' then if path == '' then

View File

@ -85,7 +85,7 @@ function Loader.get_hash(path)
end end
local function normalize(path) local function normalize(path)
return fs.normalize(path, { expand_env = false }) return fs.normalize(path, { expand_env = false, _fast = true })
end end
--- Gets the rtp excluding after directories. --- Gets the rtp excluding after directories.