Commit: patch 9.2.0096: has() function is slow due to linear feature scan

1 view
Skip to first unread message

Christian Brabandt

unread,
4:01 PM (5 hours ago) 4:01 PM
to vim...@googlegroups.com
patch 9.2.0096: has() function is slow due to linear feature scan

Commit: https://github.com/vim/vim/commit/327e0e34c907abafbf356700705a3676c036ca65
Author: Yasuhiro Matsumoto <matt...@gmail.com>
Date: Mon Mar 2 20:41:44 2026 +0000

patch 9.2.0096: has() function is slow due to linear feature scan

Problem: The has() function is slow because it performs a linear scan
of the feature list for every call.
Solution: Move common runtime checks and the patch-version parser to the
beginning of the f_has() function (Yasuhiro Matsumoto).

closes: #19550

Signed-off-by: Yasuhiro Matsumoto <matt...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/evalfunc.c b/src/evalfunc.c
index 139d940cc..3c872af56 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -7760,214 +7760,217 @@ f_has(typval_T *argvars, typval_T *rettv)
return;

name = tv_get_string(&argvars[0]);
- for (i = 0; has_list[i].name != NULL; ++i)
- if (STRICMP(name, has_list[i].name) == 0)
+
+ // Fast-path: check features not in has_list[] first to avoid the full
+ // linear scan for very common queries like has('patch-...').
+ if (STRNICMP(name, "patch", 5) == 0)
+ {
+ x = TRUE;
+ if (name[5] == '-'
+ && STRLEN(name) >= 11
+ && (name[6] >= '1' && name[6] <= '9'))
{
- x = TRUE;
- n = has_list[i].present;
- break;
+ char *end;
+ int major, minor;
+
+ // This works for patch-8.1.2, patch-9.0.3, patch-10.0.4, etc.
+ // Not for patch-9.10.5.
+ major = (int)strtoul((char *)name + 6, &end, 10);
+ if (*end == '.' && vim_isdigit(end[1])
+ && end[2] == '.' && vim_isdigit(end[3]))
+ {
+ minor = atoi(end + 1);
+
+ // Expect "patch-9.9.01234".
+ n = (major < VIM_VERSION_MAJOR
+ || (major == VIM_VERSION_MAJOR
+ && (minor < VIM_VERSION_MINOR
+ || (minor == VIM_VERSION_MINOR
+ && has_patch(atoi(end + 3))))));
+ }
}
+ else if (SAFE_isdigit(name[5]))
+ n = has_patch(atoi((char *)name + 5));
+ }
+ else if (STRICMP(name, "vim_starting") == 0)
+ {
+ x = TRUE;
+ n = (starting != 0);
+ }
+ else if (STRICMP(name, "ttyin") == 0)
+ {
+ x = TRUE;
+ n = mch_input_isatty();
+ }
+ else if (STRICMP(name, "ttyout") == 0)
+ {
+ x = TRUE;
+ n = stdout_isatty;
+ }
+ else if (STRICMP(name, "multi_byte_encoding") == 0)
+ {
+ x = TRUE;
+ n = has_mbyte;
+ }
+ else if (STRICMP(name, "gui_running") == 0)
+ {
+ x = TRUE;
+#ifdef FEAT_GUI
+ n = (gui.in_use || gui.starting);
+#endif
+ }
+ else if (STRICMP(name, "browse") == 0)
+ {
+ x = TRUE;
+#if defined(FEAT_GUI) && defined(FEAT_BROWSE)
+ n = gui.in_use; // gui_mch_browse() works when GUI is running
+#endif
+ }
+ else if (STRICMP(name, "syntax_items") == 0)
+ {
+ x = TRUE;
+#ifdef FEAT_SYN_HL
+ n = syntax_present(curwin);
+#endif
+ }
+ else if (STRICMP(name, "vcon") == 0)
+ {
+ x = TRUE;
+#ifdef FEAT_VTP
+ n = is_term_win32() && has_vtp_working();
+#endif
+ }
+ else if (STRICMP(name, "netbeans_enabled") == 0)
+ {
+ x = TRUE;
+#ifdef FEAT_NETBEANS_INTG
+ n = netbeans_active();
+#endif
+ }
+ else if (STRICMP(name, "mouse_gpm_enabled") == 0)
+ {
+ x = TRUE;
+#ifdef FEAT_MOUSE_GPM
+ n = gpm_enabled();
+#endif
+ }
+ else if (STRICMP(name, "conpty") == 0)
+ {
+ x = TRUE;
+#if defined(FEAT_TERMINAL) && defined(MSWIN)
+ n = use_conpty();
+#endif
+ }
+ else if (STRICMP(name, "clipboard_working") == 0)
+ {
+ x = TRUE;
+#ifdef FEAT_CLIPBOARD
+ n = clipmethod == CLIPMETHOD_PROVIDER ? TRUE : clip_star.available;
+#endif
+ }
+ else if (STRICMP(name, "unnamedplus") == 0)
+ {
+ x = TRUE;
+#ifdef FEAT_CLIPBOARD
+ // The + register is available when clipmethod is set to a provider,
+ // but becomes unavailable if on a platform that doesn't support it
+ // and clipmethod is "none".
+ // (Windows, MacOS).
+# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD)
+ n = TRUE;
+# elif defined(FEAT_EVAL)
+ if (clipmethod == CLIPMETHOD_PROVIDER)
+ n = TRUE;
+ else
+ n = FALSE;
+# else
+ n = FALSE;
+# endif
+#endif
+ }

- // features also in has_list[] but sometimes enabled at runtime
- if (x == TRUE && n == FALSE)
+ // Look up in has_list[] only if not already handled above.
+ if (x == FALSE)
{
- if (0)
+ for (i = 0; has_list[i].name != NULL; ++i)
+ if (STRICMP(name, has_list[i].name) == 0)
+ {
+ x = TRUE;
+ n = has_list[i].present;
+ break;
+ }
+
+ // features also in has_list[] but sometimes enabled at runtime
+ if (x == TRUE && n == FALSE)
{
- // intentionally empty
- }
+ if (0)
+ {
+ // intentionally empty
+ }
#ifdef VIMDLL
- else if (STRICMP(name, "filterpipe") == 0)
- n = gui.in_use || gui.starting;
+ else if (STRICMP(name, "filterpipe") == 0)
+ n = gui.in_use || gui.starting;
#endif
#if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
- else if (STRICMP(name, "iconv") == 0)
- n = iconv_enabled(FALSE);
+ else if (STRICMP(name, "iconv") == 0)
+ n = iconv_enabled(FALSE);
#endif
#ifdef DYNAMIC_LUA
- else if (STRICMP(name, "lua") == 0)
- n = lua_enabled(FALSE);
+ else if (STRICMP(name, "lua") == 0)
+ n = lua_enabled(FALSE);
#endif
#ifdef DYNAMIC_MZSCHEME
- else if (STRICMP(name, "mzscheme") == 0)
- n = mzscheme_enabled(FALSE);
+ else if (STRICMP(name, "mzscheme") == 0)
+ n = mzscheme_enabled(FALSE);
#endif
#ifdef DYNAMIC_PERL
- else if (STRICMP(name, "perl") == 0)
- n = perl_enabled(FALSE);
+ else if (STRICMP(name, "perl") == 0)
+ n = perl_enabled(FALSE);
#endif
#ifdef DYNAMIC_PYTHON
- else if (STRICMP(name, "python") == 0)
- n = python_enabled(FALSE);
+ else if (STRICMP(name, "python") == 0)
+ n = python_enabled(FALSE);
#endif
#ifdef DYNAMIC_PYTHON3
- else if (STRICMP(name, "python3") == 0)
- n = python3_enabled(FALSE);
+ else if (STRICMP(name, "python3") == 0)
+ n = python3_enabled(FALSE);
#endif
#if defined(DYNAMIC_PYTHON) || defined(DYNAMIC_PYTHON3)
- else if (STRICMP(name, "pythonx") == 0)
- {
+ else if (STRICMP(name, "pythonx") == 0)
+ {
# if defined(DYNAMIC_PYTHON) && defined(DYNAMIC_PYTHON3)
- if (p_pyx == 0)
- n = python3_enabled(FALSE) || python_enabled(FALSE);
- else if (p_pyx == 3)
- n = python3_enabled(FALSE);
- else if (p_pyx == 2)
- n = python_enabled(FALSE);
+ if (p_pyx == 0)
+ n = python3_enabled(FALSE) || python_enabled(FALSE);
+ else if (p_pyx == 3)
+ n = python3_enabled(FALSE);
+ else if (p_pyx == 2)
+ n = python_enabled(FALSE);
# elif defined(DYNAMIC_PYTHON)
- n = python_enabled(FALSE);
+ n = python_enabled(FALSE);
# elif defined(DYNAMIC_PYTHON3)
- n = python3_enabled(FALSE);
+ n = python3_enabled(FALSE);
# endif
- }
+ }
#endif
#ifdef DYNAMIC_RUBY
- else if (STRICMP(name, "ruby") == 0)
- n = ruby_enabled(FALSE);
+ else if (STRICMP(name, "ruby") == 0)
+ n = ruby_enabled(FALSE);
#endif
#ifdef DYNAMIC_TCL
- else if (STRICMP(name, "tcl") == 0)
- n = tcl_enabled(FALSE);
+ else if (STRICMP(name, "tcl") == 0)
+ n = tcl_enabled(FALSE);
#endif
#ifdef DYNAMIC_SODIUM
- else if (STRICMP(name, "sodium") == 0)
- n = sodium_enabled(FALSE);
+ else if (STRICMP(name, "sodium") == 0)
+ n = sodium_enabled(FALSE);
#endif
#if defined(FEAT_TERMINAL) && defined(MSWIN)
- else if (STRICMP(name, "terminal") == 0)
- n = terminal_enabled();
+ else if (STRICMP(name, "terminal") == 0)
+ n = terminal_enabled();
#endif
#ifdef DYNAMIC_GPM
- else if (STRICMP(name, "mouse_gpm") == 0)
- n = gpm_available();
-#endif
- }
-
- // features not in has_list[]
- if (x == FALSE)
- {
- if (STRNICMP(name, "patch", 5) == 0)
- {
- x = TRUE;
- if (name[5] == '-'
- && STRLEN(name) >= 11
- && (name[6] >= '1' && name[6] <= '9'))
- {
- char *end;
- int major, minor;
-
- // This works for patch-8.1.2, patch-9.0.3, patch-10.0.4, etc.
- // Not for patch-9.10.5.
- major = (int)strtoul((char *)name + 6, &end, 10);
- if (*end == '.' && vim_isdigit(end[1])
- && end[2] == '.' && vim_isdigit(end[3]))
- {
- minor = atoi(end + 1);
-
- // Expect "patch-9.9.01234".
- n = (major < VIM_VERSION_MAJOR
- || (major == VIM_VERSION_MAJOR
- && (minor < VIM_VERSION_MINOR
- || (minor == VIM_VERSION_MINOR
- && has_patch(atoi(end + 3))))));
- }
- }
- else if (SAFE_isdigit(name[5]))
- n = has_patch(atoi((char *)name + 5));
- }
- else if (STRICMP(name, "vim_starting") == 0)
- {
- x = TRUE;
- n = (starting != 0);
- }
- else if (STRICMP(name, "ttyin") == 0)
- {
- x = TRUE;
- n = mch_input_isatty();
- }
- else if (STRICMP(name, "ttyout") == 0)
- {
- x = TRUE;
- n = stdout_isatty;
- }
- else if (STRICMP(name, "multi_byte_encoding") == 0)
- {
- x = TRUE;
- n = has_mbyte;
- }
- else if (STRICMP(name, "gui_running") == 0)
- {
- x = TRUE;
-#ifdef FEAT_GUI
- n = (gui.in_use || gui.starting);
-#endif
- }
- else if (STRICMP(name, "browse") == 0)
- {
- x = TRUE;
-#if defined(FEAT_GUI) && defined(FEAT_BROWSE)
- n = gui.in_use; // gui_mch_browse() works when GUI is running
-#endif
- }
- else if (STRICMP(name, "syntax_items") == 0)
- {
- x = TRUE;
-#ifdef FEAT_SYN_HL
- n = syntax_present(curwin);
-#endif
- }
- else if (STRICMP(name, "vcon") == 0)
- {
- x = TRUE;
-#ifdef FEAT_VTP
- n = is_term_win32() && has_vtp_working();
-#endif
- }
- else if (STRICMP(name, "netbeans_enabled") == 0)
- {
- x = TRUE;
-#ifdef FEAT_NETBEANS_INTG
- n = netbeans_active();
-#endif
- }
- else if (STRICMP(name, "mouse_gpm_enabled") == 0)
- {
- x = TRUE;
-#ifdef FEAT_MOUSE_GPM
- n = gpm_enabled();
-#endif
- }
- else if (STRICMP(name, "conpty") == 0)
- {
- x = TRUE;
-#if defined(FEAT_TERMINAL) && defined(MSWIN)
- n = use_conpty();
-#endif
- }
- else if (STRICMP(name, "clipboard_working") == 0)
- {
- x = TRUE;
-#ifdef FEAT_CLIPBOARD
- n = clipmethod == CLIPMETHOD_PROVIDER ? TRUE : clip_star.available;
-#endif
- }
- else if (STRICMP(name, "unnamedplus") == 0)
- {
- x = TRUE;
-#ifdef FEAT_CLIPBOARD
- // The + register is available when clipmethod is set to a provider,
- // but becomes unavailable if on a platform that doesn't support it
- // and clipmethod is "none".
- // (Windows, MacOS).
-# if defined(FEAT_X11) || defined(FEAT_WAYLAND_CLIPBOARD)
- n = TRUE;
-# elif defined(FEAT_EVAL)
- if (clipmethod == CLIPMETHOD_PROVIDER)
- n = TRUE;
- else
- n = FALSE;
-# else
- n = FALSE;
-# endif
+ else if (STRICMP(name, "mouse_gpm") == 0)
+ n = gpm_available();
#endif
}
}
diff --git a/src/version.c b/src/version.c
index dde1211ce..31e444098 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 96,
/**/
95,
/**/
Reply all
Reply to author
Forward
0 new messages