commit 5e64d92411737a8fe0ea44faebf986944bfea8ea
parent ba1cc9e10c67e9cf8873ba67b2c0d6b8d5ecd6dd
Author: monkoose <pythonproof@gmail.com>
Date: Thu, 22 May 2025 13:00:22 +0300
perf(runtime): vim.trim for long only whitespace strings
Diffstat:
3 files changed, 76 insertions(+), 2 deletions(-)
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
@@ -801,7 +801,9 @@ end
---@return string String with whitespace removed from its beginning and end
function vim.trim(s)
vim.validate('s', s, 'string')
- return s:match('^%s*(.*%S)') or ''
+ -- `s:match('^%s*(.*%S)')` is slow for long whitespace strings,
+ -- so we are forced to split it into two parts to prevent this
+ return s:gsub('^%s+', ''):match('^.*%S') or ''
end
--- Escapes magic chars in |lua-pattern|s.
diff --git a/test/benchmark/trim_spec.lua b/test/benchmark/trim_spec.lua
@@ -0,0 +1,70 @@
+describe('vim.trim()', function()
+ --- @param t number[]
+ local function mean(t)
+ assert(#t > 0)
+ local sum = 0
+ for _, v in ipairs(t) do
+ sum = sum + v
+ end
+ return sum / #t
+ end
+
+ --- @param t number[]
+ local function median(t)
+ local len = #t
+ if len % 2 == 0 then
+ return t[len / 2]
+ end
+ return t[(len + 1) / 2]
+ end
+
+ --- @param f fun(t: number[]): table<number, number|string|table>
+ local function measure(f, input, N)
+ local stats = {} ---@type number[]
+ for _ = 1, N do
+ local tic = vim.uv.hrtime()
+ f(input)
+ local toc = vim.uv.hrtime()
+ stats[#stats + 1] = (toc - tic) / 1000000
+ end
+ table.sort(stats)
+ print(
+ string.format(
+ '\nN: %d, Min: %0.6f ms, Max: %0.6f ms, Median: %0.6f ms, Mean: %0.6f ms',
+ N,
+ math.min(unpack(stats)),
+ math.max(unpack(stats)),
+ median(stats),
+ mean(stats)
+ )
+ )
+ end
+
+ local strings = {
+ ['10000 whitespace characters'] = string.rep(' ', 10000),
+ ['10000 whitespace characters and one non-whitespace at the end'] = string.rep(' ', 10000)
+ .. '0',
+ ['10000 whitespace characters and one non-whitespace at the start'] = '0'
+ .. string.rep(' ', 10000),
+ ['10000 non-whitespace characters'] = string.rep('0', 10000),
+ ['10000 whitespace and one non-whitespace in the middle'] = string.rep(' ', 5000)
+ .. 'a'
+ .. string.rep(' ', 5000),
+ ['10000 whitespace characters surrounded by non-whitespace'] = '0'
+ .. string.rep(' ', 10000)
+ .. '0',
+ ['10000 non-whitespace characters surrounded by whitespace'] = ' '
+ .. string.rep('0', 10000)
+ .. ' ',
+ }
+
+ --- @type string[]
+ local string_names = vim.tbl_keys(strings)
+ table.sort(string_names)
+
+ for _, name in ipairs(string_names) do
+ it(name, function()
+ measure(vim.trim, strings[name], 100)
+ end)
+ end
+end)
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
@@ -719,10 +719,12 @@ describe('lua stdlib', function()
{ ' b ', 'b' },
{ '\tc', 'c' },
{ 'r\n', 'r' },
+ { '', '' },
+ { ' \t \n', '' },
}
for _, q in ipairs(trims) do
- assert(q[2], trim(q[1]))
+ eq(q[2], trim(q[1]))
end
-- Validates args.