mirror of
https://github.com/neovim/neovim.git
synced 2026-03-30 20:32:08 +00:00
perf(runtime): hardware accelerated "packadd opt_package"
fixes #37586 when doing `packadd mypackage` up to two exact paths are added to &rtp. Instead of recalculating runtime_search_path from scratch, we can "just" splice these two paths in This is simple in theory, but get complicated in practice as "after" dirs do exist and need some wrangling. Echasnovski did some benchmarking, to show that this reduces overhead of a init.lua configuration style where separate `packadd!` calls are used spread out during the config. In addition, "batched" addition (either using "start" packages or packadd! a lot of opt packages at once) does not regress. A theoretical simplification could be to NEVER explicitly add "after" dirs to &rtp, but implicitly add all existing "after" dirs in reverse order when calculating the effective run time path. This might be tricky to do without breaking 12 tpope plugins again tho. We might also instead consider solutions where &rtp remains fully expanded but no longer is the main source of truth. But this is all post 0.12 work. This PR is an alright stopgap to make 0.12 fully support intended use cases of vim.pack.add() .
This commit is contained in:
@@ -372,6 +372,10 @@ PERFORMANCE
|
||||
• |i_CTRL-R| inserts named/clipboard registers literally, 10x speedup.
|
||||
• LSP `textDocument/semanticTokens/range` is supported, which requests tokens
|
||||
for the viewport (visible screen) only.
|
||||
• |:packadd| doesn't invalidate the cached Lua package path. Instead the cache
|
||||
gets updated in place. This might make a big startuptime difference for
|
||||
certain |init.lua| patterns where multiple |:packadd| or |vim.pack.add()|
|
||||
calls are interspersed with other code.
|
||||
|
||||
PLUGINS
|
||||
|
||||
|
||||
@@ -94,7 +94,9 @@ typedef struct {
|
||||
typedef struct {
|
||||
char *path;
|
||||
bool after;
|
||||
bool pack_inserted;
|
||||
TriState has_lua;
|
||||
size_t pos_in_rtp;
|
||||
} SearchPathItem;
|
||||
|
||||
typedef kvec_t(SearchPathItem) RuntimeSearchPath;
|
||||
@@ -292,6 +294,7 @@ void f_getstacktrace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
}
|
||||
|
||||
static bool runtime_search_path_valid = false;
|
||||
static bool runtime_search_path_valid_thread = false;
|
||||
static int *runtime_search_path_ref = NULL;
|
||||
static RuntimeSearchPath runtime_search_path;
|
||||
static RuntimeSearchPath runtime_search_path_thread;
|
||||
@@ -531,8 +534,9 @@ static RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
|
||||
{
|
||||
RuntimeSearchPath dst = KV_INITIAL_VALUE;
|
||||
for (size_t j = 0; j < kv_size(src); j++) {
|
||||
SearchPathItem src_item = kv_A(src, j);
|
||||
kv_push(dst, ((SearchPathItem){ xstrdup(src_item.path), src_item.after, src_item.has_lua }));
|
||||
SearchPathItem item = kv_A(src, j);
|
||||
kv_push(dst, ((SearchPathItem){ xstrdup(item.path), item.after, item.pack_inserted,
|
||||
item.has_lua, item.pos_in_rtp }));
|
||||
}
|
||||
|
||||
return dst;
|
||||
@@ -642,13 +646,20 @@ Array runtime_inspect(Arena *arena)
|
||||
|
||||
for (size_t i = 0; i < kv_size(path); i++) {
|
||||
SearchPathItem *item = &kv_A(path, i);
|
||||
Array entry = arena_array(arena, 3);
|
||||
ADD_C(entry, CSTR_AS_OBJ(item->path));
|
||||
ADD_C(entry, BOOLEAN_OBJ(item->after));
|
||||
if (item->has_lua != kNone) {
|
||||
ADD_C(entry, BOOLEAN_OBJ(item->has_lua == kTrue));
|
||||
Dict entry = arena_dict(arena, 5);
|
||||
PUT_C(entry, "path", CSTR_AS_OBJ(item->path));
|
||||
if (item->after) {
|
||||
PUT_C(entry, "after", BOOLEAN_OBJ(true));
|
||||
}
|
||||
ADD_C(rv, ARRAY_OBJ(entry));
|
||||
if (item->pack_inserted) {
|
||||
PUT_C(entry, "pack_inserted", BOOLEAN_OBJ(true));
|
||||
}
|
||||
if (item->has_lua != kNone) {
|
||||
PUT_C(entry, "has_lua", BOOLEAN_OBJ(item->has_lua == kTrue));
|
||||
}
|
||||
PUT_C(entry, "pos_in_rtp", INTEGER_OBJ((Integer)item->pos_in_rtp));
|
||||
|
||||
ADD_C(rv, DICT_OBJ(entry));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
@@ -752,25 +763,27 @@ int do_in_path_and_pp(char *path, char *name, int flags, DoInRuntimepathCB callb
|
||||
return done;
|
||||
}
|
||||
|
||||
static void push_path(RuntimeSearchPath *search_path, Set(String) *rtp_used, char *entry,
|
||||
bool after)
|
||||
static bool push_path(RuntimeSearchPath *search_path, Set(String) *rtp_used, char *entry,
|
||||
bool after, size_t pos_in_rtp)
|
||||
{
|
||||
String *key_alloc;
|
||||
if (set_put_ref(String, rtp_used, cstr_as_string(entry), &key_alloc)) {
|
||||
*key_alloc = cstr_to_string(entry);
|
||||
kv_push(*search_path, ((SearchPathItem){ key_alloc->data, after, kNone }));
|
||||
kv_push(*search_path, ((SearchPathItem){ key_alloc->data, after, false, kNone, pos_in_rtp }));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void expand_rtp_entry(RuntimeSearchPath *search_path, Set(String) *rtp_used, char *entry,
|
||||
bool after)
|
||||
bool after, size_t pos_in_rtp)
|
||||
{
|
||||
if (set_has(String, rtp_used, cstr_as_string(entry))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!*entry) {
|
||||
push_path(search_path, rtp_used, entry, after);
|
||||
push_path(search_path, rtp_used, entry, after, pos_in_rtp);
|
||||
}
|
||||
|
||||
int num_files;
|
||||
@@ -778,14 +791,16 @@ static void expand_rtp_entry(RuntimeSearchPath *search_path, Set(String) *rtp_us
|
||||
char *(pat[]) = { entry };
|
||||
if (gen_expand_wildcards(1, pat, &num_files, &files, EW_DIR | EW_NOBREAK) == OK) {
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
push_path(search_path, rtp_used, files[i], after);
|
||||
// reuses position but it is ok, we need to be monotonic but not strictly
|
||||
push_path(search_path, rtp_used, files[i], after, pos_in_rtp);
|
||||
}
|
||||
FreeWild(num_files, files);
|
||||
}
|
||||
}
|
||||
|
||||
static void expand_pack_entry(RuntimeSearchPath *search_path, Set(String) *rtp_used,
|
||||
CharVec *after_path, char *pack_entry, size_t pack_entry_len)
|
||||
CharVec *after_path, char *pack_entry, size_t pack_entry_len,
|
||||
size_t pos_in_rtp)
|
||||
{
|
||||
static char buf[MAXPATHL];
|
||||
char *(start_pat[]) = { "/pack/*/start/*", "/start/*" }; // NOLINT
|
||||
@@ -795,7 +810,7 @@ static void expand_pack_entry(RuntimeSearchPath *search_path, Set(String) *rtp_u
|
||||
}
|
||||
xstrlcpy(buf, pack_entry, sizeof buf);
|
||||
xstrlcpy(buf + pack_entry_len, start_pat[i], sizeof buf - pack_entry_len);
|
||||
expand_rtp_entry(search_path, rtp_used, buf, false);
|
||||
expand_rtp_entry(search_path, rtp_used, buf, false, pos_in_rtp);
|
||||
size_t after_size = strlen(buf) + 7;
|
||||
char *after = xmallocz(after_size);
|
||||
xstrlcpy(after, buf, after_size);
|
||||
@@ -844,34 +859,45 @@ static RuntimeSearchPath runtime_search_path_build(void)
|
||||
break;
|
||||
}
|
||||
|
||||
size_t pos_in_rtp = (size_t)(cur_entry - p_rtp);
|
||||
|
||||
// fact: &rtp entries can contain wild chars
|
||||
expand_rtp_entry(&search_path, &rtp_used, buf, false);
|
||||
expand_rtp_entry(&search_path, &rtp_used, buf, false, pos_in_rtp);
|
||||
|
||||
handle_T *h = map_ref(String, int)(&pack_used, cstr_as_string(buf), NULL);
|
||||
if (h) {
|
||||
(*h)++;
|
||||
expand_pack_entry(&search_path, &rtp_used, &after_path, buf, buflen);
|
||||
expand_pack_entry(&search_path, &rtp_used, &after_path, buf, buflen, pos_in_rtp);
|
||||
}
|
||||
}
|
||||
|
||||
// The following entries were not explicit in rtp.
|
||||
// this is fine, but keep pos_in_rtp monotonic:
|
||||
// use the comma between two entries as a sentinel
|
||||
size_t sentinel_pos_in_rtp = (size_t)(rtp_entry - p_rtp);
|
||||
sentinel_pos_in_rtp -= (sentinel_pos_in_rtp > 0) ? 1 : 0;
|
||||
|
||||
for (size_t i = 0; i < kv_size(pack_entries); i++) {
|
||||
String item = kv_A(pack_entries, i);
|
||||
handle_T h = map_get(String, int)(&pack_used, item);
|
||||
if (h == 0) {
|
||||
expand_pack_entry(&search_path, &rtp_used, &after_path, item.data, item.size);
|
||||
expand_pack_entry(&search_path, &rtp_used, &after_path, item.data, item.size,
|
||||
sentinel_pos_in_rtp);
|
||||
}
|
||||
}
|
||||
|
||||
// "after" packages
|
||||
for (size_t i = 0; i < kv_size(after_path); i++) {
|
||||
expand_rtp_entry(&search_path, &rtp_used, kv_A(after_path, i), true);
|
||||
expand_rtp_entry(&search_path, &rtp_used, kv_A(after_path, i), true, sentinel_pos_in_rtp);
|
||||
xfree(kv_A(after_path, i));
|
||||
}
|
||||
|
||||
// "after" dirs in rtp
|
||||
for (; *rtp_entry != NUL;) {
|
||||
char *cur_entry = rtp_entry;
|
||||
copy_option_part(&rtp_entry, buf, MAXPATHL, ",");
|
||||
expand_rtp_entry(&search_path, &rtp_used, buf, path_is_after(buf, strlen(buf)));
|
||||
size_t pos_in_rtp = (size_t)(cur_entry - p_rtp);
|
||||
expand_rtp_entry(&search_path, &rtp_used, buf, path_is_after(buf, strlen(buf)), pos_in_rtp);
|
||||
}
|
||||
|
||||
// strings are not owned
|
||||
@@ -914,13 +940,23 @@ void runtime_search_path_validate(void)
|
||||
runtime_search_path = runtime_search_path_build();
|
||||
runtime_search_path_valid = true;
|
||||
runtime_search_path_ref = NULL; // initially unowned
|
||||
uv_mutex_lock(&runtime_search_path_mutex);
|
||||
runtime_search_path_free(runtime_search_path_thread);
|
||||
runtime_search_path_thread = copy_runtime_search_path(runtime_search_path);
|
||||
uv_mutex_unlock(&runtime_search_path_mutex);
|
||||
update_runtime_search_path_thread(true);
|
||||
}
|
||||
}
|
||||
|
||||
void update_runtime_search_path_thread(bool force)
|
||||
{
|
||||
if (!force && !(runtime_search_path_valid && !runtime_search_path_valid_thread)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uv_mutex_lock(&runtime_search_path_mutex);
|
||||
runtime_search_path_free(runtime_search_path_thread);
|
||||
runtime_search_path_thread = copy_runtime_search_path(runtime_search_path);
|
||||
uv_mutex_unlock(&runtime_search_path_mutex);
|
||||
runtime_search_path_valid_thread = true;
|
||||
}
|
||||
|
||||
/// Just like do_in_path_and_pp(), using 'runtimepath' for "path".
|
||||
int do_in_runtimepath(char *name, int flags, DoInRuntimepathCB callback, void *cookie)
|
||||
{
|
||||
@@ -1012,8 +1048,8 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
|
||||
}
|
||||
|
||||
// now we have:
|
||||
// rtp/pack/name/start/name
|
||||
// p4 p3 p2 p1
|
||||
// rtp/pack/name/(start|opt)/name
|
||||
// p4 p3 p2 p1
|
||||
//
|
||||
// find the part up to "pack" in 'runtimepath'
|
||||
p4++; // append pathsep in order to expand symlink
|
||||
@@ -1091,10 +1127,12 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
|
||||
// We now have 'rtp' parts: {keep}{keep_after}{rest}.
|
||||
// Create new_rtp, first: {keep},{fname}
|
||||
size_t keep = (size_t)(insp - p_rtp);
|
||||
size_t first_pos = keep;
|
||||
memmove(new_rtp, p_rtp, keep);
|
||||
size_t new_rtp_len = keep;
|
||||
if (*insp == NUL) {
|
||||
new_rtp[new_rtp_len++] = ','; // add comma before
|
||||
first_pos++;
|
||||
}
|
||||
memmove(new_rtp + new_rtp_len, fname, addlen - 1);
|
||||
new_rtp_len += addlen - 1;
|
||||
@@ -1102,6 +1140,8 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
|
||||
new_rtp[new_rtp_len++] = ','; // add comma after
|
||||
}
|
||||
|
||||
size_t after_pos = 0;
|
||||
|
||||
if (afterlen > 0 && after_insp != NULL) {
|
||||
size_t keep_after = (size_t)(after_insp - p_rtp);
|
||||
|
||||
@@ -1112,6 +1152,7 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
|
||||
new_rtp_len += afterlen - 1;
|
||||
new_rtp[new_rtp_len++] = ',';
|
||||
keep = keep_after;
|
||||
after_pos = keep_after;
|
||||
}
|
||||
|
||||
if (p_rtp[keep] != NUL) {
|
||||
@@ -1124,11 +1165,51 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
|
||||
if (afterlen > 0 && after_insp == NULL) {
|
||||
// Append afterdir when "after" was not found:
|
||||
// {keep},{fname}{rest},{afterdir}
|
||||
xstrlcat(new_rtp, ",", new_rtp_capacity);
|
||||
after_pos = xstrlcat(new_rtp, ",", new_rtp_capacity);
|
||||
xstrlcat(new_rtp, afterdir, new_rtp_capacity);
|
||||
}
|
||||
|
||||
bool was_valid = runtime_search_path_valid;
|
||||
set_option_value_give_err(kOptRuntimepath, CSTR_AS_OPTVAL(new_rtp), 0);
|
||||
|
||||
assert(!runtime_search_path_valid);
|
||||
// If this is the result of "packadd opt_pack", rebuilding runtime_search_pat
|
||||
// from scratch is needlessly slow. splice in the package and its afterdir instead.
|
||||
// But don't do this for "pack/*/start/*" (is_pack=true):
|
||||
// we want properly expand wildcards in a "start" bundle.
|
||||
if (was_valid && !is_pack) {
|
||||
runtime_search_path_valid = true;
|
||||
runtime_search_path_valid_thread = false;
|
||||
kv_pushp(runtime_search_path);
|
||||
ssize_t i = (ssize_t)(kv_size(runtime_search_path)) - 1;
|
||||
|
||||
if (afterlen > 0) {
|
||||
kv_pushp(runtime_search_path);
|
||||
i += 1;
|
||||
for (; i >= 1; i--) {
|
||||
if (i > 1 && kv_A(runtime_search_path, i - 2).pos_in_rtp >= after_pos) {
|
||||
kv_A(runtime_search_path, i) = kv_A(runtime_search_path, i - 2);
|
||||
kv_A(runtime_search_path, i).pos_in_rtp += addlen + afterlen;
|
||||
} else {
|
||||
kv_A(runtime_search_path, i) = (SearchPathItem){ xstrdup(afterdir), true, true, kNone,
|
||||
after_pos + addlen };
|
||||
i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; i >= 0; i--) {
|
||||
if (i > 0 && kv_A(runtime_search_path, i - 1).pos_in_rtp >= first_pos) {
|
||||
kv_A(runtime_search_path, i) = kv_A(runtime_search_path, i - 1);
|
||||
kv_A(runtime_search_path, i).pos_in_rtp += addlen;
|
||||
} else {
|
||||
kv_A(runtime_search_path, i) = (SearchPathItem){ xstrdup(fname), false, true, kNone,
|
||||
first_pos };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
xfree(new_rtp);
|
||||
retval = OK;
|
||||
|
||||
@@ -1280,6 +1361,8 @@ void load_start_packages(void)
|
||||
add_start_pack_plugins, &APP_LOAD);
|
||||
do_in_path(p_pp, "", "start/*", DIP_ALL + DIP_DIR, // NOLINT
|
||||
add_start_pack_plugins, &APP_LOAD);
|
||||
|
||||
update_runtime_search_path_thread(false);
|
||||
}
|
||||
|
||||
// ":packloadall"
|
||||
@@ -1347,6 +1430,8 @@ void ex_packadd(exarg_T *eap)
|
||||
do_in_path(p_pp, "", pat, DIP_ALL + DIP_DIR + (res == FAIL ? DIP_ERR : 0),
|
||||
add_opt_pack_plugins, cookie);
|
||||
|
||||
update_runtime_search_path_thread(false);
|
||||
|
||||
xfree(pat);
|
||||
}
|
||||
|
||||
|
||||
@@ -1128,6 +1128,44 @@ describe('startup', function()
|
||||
}, exec_lua [[ return _G.test_loadorder ]])
|
||||
end)
|
||||
|
||||
it('does an incremental update for packadd', function()
|
||||
pack_clear [[ lua _G.test_loadorder = {} ]]
|
||||
command [[
|
||||
" need to use the runtime to make the initial cache:
|
||||
runtime! non_exist_ent
|
||||
" this should now incrementally update it:
|
||||
packadd! superspecial
|
||||
]]
|
||||
|
||||
local check = api.nvim__runtime_inspect()
|
||||
local check_copy = vim.deepcopy(check)
|
||||
local any_incremental = false
|
||||
for _, item in ipairs(check_copy) do
|
||||
any_incremental = any_incremental or item.pack_inserted
|
||||
item.pack_inserted = nil
|
||||
end
|
||||
eq(true, any_incremental, 'no pack_inserted in ' .. vim.inspect(check))
|
||||
|
||||
command [[
|
||||
let &rtp = &rtp
|
||||
runtime! phantom_ghost
|
||||
]]
|
||||
|
||||
local new_check = api.nvim__runtime_inspect()
|
||||
eq(check_copy, new_check)
|
||||
|
||||
command [[ runtime! filen.lua ]]
|
||||
eq({
|
||||
'ordinary',
|
||||
'SuperSpecial',
|
||||
'FANCY',
|
||||
'mittel',
|
||||
'FANCY after',
|
||||
'SuperSpecial after',
|
||||
'ordinary after',
|
||||
}, exec_lua [[ return _G.test_loadorder ]])
|
||||
end)
|
||||
|
||||
it('handles the correct order with opt packages and globpath(&rtp, ...)', function()
|
||||
pack_clear [[ set loadplugins | lua _G.test_loadorder = {} ]]
|
||||
command [[
|
||||
|
||||
Reference in New Issue
Block a user