patch 9.2.0053: Vims list concatenation is inefficient
Commit:
https://github.com/vim/vim/commit/048079f6dab8f0eb96f7d8090ccc2b8797ea4a09
Author: Yasuhiro Matsumoto <
matt...@gmail.com>
Date: Wed Feb 25 19:31:37 2026 +0000
patch 9.2.0053: Vims list concatenation is inefficient
Problem: Vims list concatenation is inefficient
Solution: Use a single allocation of len1 + len2 using
list_alloc_with_items() (Yasuhiro Matsumoto).
Replace list_copy() + list_extend() (N+1 individual mallocs) with a
single list_alloc_with_items(len1+len2) call. This reduces the number
of memory allocations from O(N) to O(1) for the list '+' operator.
closes: #19495
Signed-off-by: Yasuhiro Matsumoto <
matt...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/list.c b/src/list.c
index 253b1be9f..310d8516b 100644
--- a/src/list.c
+++ b/src/list.c
@@ -1232,22 +1232,62 @@ list_extend(list_T *l1, list_T *l2, listitem_T *bef)
list_concat(list_T *l1, list_T *l2, typval_T *tv)
{
list_T *l;
+ int len1 = l1 == NULL ? 0 : l1->lv_len;
+ int len2 = l2 == NULL ? 0 : l2->lv_len;
+ long totallen = (long)len1 + (long)len2;
+ int i;
+ listitem_T *item;
- // make a copy of the first list.
- if (l1 == NULL)
+ if (totallen == 0)
+ {
l = list_alloc();
- else
- l = list_copy(l1, FALSE, TRUE, 0);
+ if (l == NULL)
+ return FAIL;
+ ++l->lv_refcount;
+ tv->v_type = VAR_LIST;
+ tv->v_lock = 0;
+ tv->vval.v_list = l;
+ return OK;
+ }
+ if (totallen > INT_MAX)
+ return FAIL;
+
+ // allocate all items at once for efficiency
+ l = list_alloc_with_items((int)totallen);
if (l == NULL)
return FAIL;
+
+ i = 0;
+ if (len1 > 0)
+ {
+ CHECK_LIST_MATERIALIZE(l1);
+ for (item = l1->lv_first; item != NULL && !got_int;
+ item = item->li_next)
+ {
+ typval_T new_tv;
+
+ copy_tv(&item->li_tv, &new_tv);
+ list_set_item(l, i++, &new_tv);
+ }
+ }
+ if (len2 > 0)
+ {
+ CHECK_LIST_MATERIALIZE(l2);
+ for (item = l2->lv_first; item != NULL && !got_int;
+ item = item->li_next)
+ {
+ typval_T new_tv;
+
+ copy_tv(&item->li_tv, &new_tv);
+ list_set_item(l, i++, &new_tv);
+ }
+ }
+
+ ++l->lv_refcount;
tv->v_type = VAR_LIST;
tv->v_lock = 0;
tv->vval.v_list = l;
- if (l1 == NULL)
- ++l->lv_refcount;
-
- // append all items from the second list
- return list_extend(l, l2, NULL);
+ return OK;
}
list_T *
@@ -1482,7 +1522,7 @@ list_join_inner(
{
int i;
join_T *p;
- int sumlen = 0;
+ long sumlen = 0;
int first = TRUE;
char_u *tofree;
char_u numbuf[NUMBUFLEN];
@@ -1500,7 +1540,7 @@ list_join_inner(
return FAIL;
s.length = STRLEN(s.string);
- sumlen += (int)s.length;
+ sumlen += (long)s.length;
(void)ga_grow(join_gap, 1);
p = ((join_T *)join_gap->ga_data) + (join_gap->ga_len++);
@@ -1526,8 +1566,8 @@ list_join_inner(
// multiple copy operations. Add 2 for a tailing ']' and NUL.
seplen = STRLEN(sep);
if (join_gap->ga_len >= 2)
- sumlen += (int)seplen * (join_gap->ga_len - 1);
- if (ga_grow(gap, sumlen + 2) == FAIL)
+ sumlen += (long)seplen * (join_gap->ga_len - 1);
+ if (sumlen > INT_MAX - 2 || ga_grow(gap, (int)sumlen + 2) == FAIL)
return FAIL;
for (i = 0; i < join_gap->ga_len && !got_int; ++i)
@@ -1847,11 +1887,16 @@ f_list2str(typval_T *argvars, typval_T *rettv)
}
ga_append(&ga, NUL);
}
- else if (ga_grow(&ga, list_len(l) + 1) == OK)
+ else
{
- FOR_ALL_LIST_ITEMS(l, li)
- ga_append(&ga, tv_get_number(&li->li_tv));
- ga_append(&ga, NUL);
+ long len = (long)list_len(l) + 1;
+
+ if (len <= INT_MAX && ga_grow(&ga, (int)len) == OK)
+ {
+ FOR_ALL_LIST_ITEMS(l, li)
+ ga_append(&ga, tv_get_number(&li->li_tv));
+ ga_append(&ga, NUL);
+ }
}
rettv->v_type = VAR_STRING;
diff --git a/src/version.c b/src/version.c
index 36d32a15a..fe8ce15a1 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 */
+/**/
+ 53,
/**/
52,
/**/