neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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:
Mruntime/lua/vim/shared.lua | 4+++-
Atest/benchmark/trim_spec.lua | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/functional/lua/vim_spec.lua | 4+++-
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.