bug#81334: 32.0.50; fontifying buffers with many blocks is very slow

Yuan Fu <[email protected]>
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>

> On Jul 25, 2026, at 10:40 PM, Eli Zaretskii <[email protected]> wrote:
> 
>> From: Yuan Fu <[email protected]>
>> Date: Sat, 25 Jul 2026 15:47:33 -0700
>> Cc: [email protected],
>> [email protected],
>> [email protected],
>> [email protected],
>> [email protected],
>> [email protected]
>> 
>> 
>> 
>>> On Jul 18, 2026, at 1:55 AM, Eli Zaretskii <[email protected]> wrote:
>>> 
>>> Ping!  Any progress?
>> 
>> The patch makes sense to me. The cache is allocated on the heap  rather than a fixed length LRU, which is ok, since the code is simpler that way, and I don’t expect there to be too many languages loaded. Plus, if a language is loaded, it’s likely it’ll be used throughout the session.
> 
> Which patch you think we should install?  Rahul posted 2 patches.

This one (see attached). I took liberty to edit some of the comments.

> 
>> I do wonder if we want to put an arbitrary upper limit on how many language we can load, like 1000? (And signal an error if the limit is reached; basically, you can’t load more languages than 1000.) Normal usage shouldn’t reach that limit, and if something strange happens, we have an upper bound on how much memory we consume.
> 
> Why isn't the memory limit enough to do this?  That way, each user can
> use the number of languages his/her system can support.

Just a knee-jerk reaction to “unbounded cache, scary” :) 

Yuan
language-cache.patch (application/octet-stream, 3.6 KB)
From 996c06379b0553ce42c3a9f2fbc03040da91bf62 Mon Sep 17 00:00:00 2001
From: Rahul Martim Juliato <[email protected]>
Date: Sat, 4 Jul 2026 10:03:39 -0300
Subject: [PATCH] Cache resolved tree-sitter languages (bug#81334)

* src/treesit.c (treesit_lang_cache_get)
(treesit_lang_cache_put): New functions implementing a
per-session cache of resolved languages.
(treesit_load_language): Consult and populate the cache, to
avoid repeating the dlopen/dlsym and ABI-version check on every
parser creation.
---
 src/treesit.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/src/treesit.c b/src/treesit.c
index 73094bfbbdf..09a9006b876 100644
--- a/src/treesit.c
+++ b/src/treesit.c
@@ -632,6 +632,56 @@ treesit_debug_print_parser_list (char *msg, Lisp_Object parser)
   const char *filename;
 };
 
+/* Cache of loaded languages, keyed by language symbols.  Resolving a
+   language does dlopen + dlsym plus an ABI-version check that creates a
+   throwaway TSParser; repeating that for every parser created is
+   expensive when a mode creates many embedded parsers (e.g. one
+   markdown-inline parser per block).  We don't support
+   unloading/reloading languages so we can cache loaded languages for
+   the entire session; in fact, we _want_ to cache the language object
+   so it stays stable for the whole session.  */
+struct treesit_lang_cache_entry
+{
+  /* Symbol for the cached langauge, never gc'ed.  */
+  Lisp_Object symbol;
+  /* The cached language object.  */
+  struct treesit_loaded_lang lang;
+};
+
+/* The cache, a monotonically growing array on the heap.  */
+static struct treesit_lang_cache_entry *treesit_lang_cache = NULL;
+/* Number of available slots in the allocated space.  */
+static ptrdiff_t treesit_lang_cache_size = 0;
+/* Number of existing cache entry in the array.  */
+static ptrdiff_t treesit_lang_cache_used = 0;
+
+/* Return the cached language object for LANGUAGE_SYMBOL, or {NULL,
+   NULL} if there is none.  */
+static struct treesit_loaded_lang
+treesit_lang_cache_get (Lisp_Object language_symbol)
+{
+  for (ptrdiff_t i = 0; i < treesit_lang_cache_used; i++)
+    if (EQ (treesit_lang_cache[i].symbol, language_symbol))
+      return treesit_lang_cache[i].lang;
+  return (struct treesit_loaded_lang) { NULL, NULL };
+}
+
+/* Cache LOADED_LANG keyed by LANGUAGE_SYMBOL.  */
+static void
+treesit_lang_cache_put (Lisp_Object language_symbol,
+			struct treesit_loaded_lang loaded_lang)
+{
+  if (treesit_lang_cache_used == treesit_lang_cache_size)
+    {
+      treesit_lang_cache
+	= xpalloc (treesit_lang_cache, &treesit_lang_cache_size, 1, -1,
+		   sizeof *treesit_lang_cache);
+    }
+  treesit_lang_cache[treesit_lang_cache_used].symbol = language_symbol;
+  treesit_lang_cache[treesit_lang_cache_used].lang = loaded_lang;
+  treesit_lang_cache_used++;
+}
+
 /* Translate a symbol treesit-<lang> to a C name treesit_<lang>.  */
 static void
 treesit_symbol_to_c_name (char *symbol_name)
@@ -762,6 +812,11 @@ treesit_language_abi_version (const TSLanguage *ts_lang)
 treesit_load_language (Lisp_Object language_symbol,
 		       Lisp_Object *signal_symbol, Lisp_Object *signal_data)
 {
+  struct treesit_loaded_lang cached
+    = treesit_lang_cache_get (language_symbol);
+  if (cached.lang != NULL)
+    return cached;
+
   Lisp_Object symbol_name = Fsymbol_name (language_symbol);
 
   CHECK_LIST (Vtreesit_extra_load_path);
@@ -886,6 +941,7 @@ treesit_load_language (Lisp_Object language_symbol,
   dynlib_addr ((void (*)) langfn, &loaded_lang.filename, &sym);
 
   loaded_lang.lang = lang;
+  treesit_lang_cache_put (language_symbol, loaded_lang);
   return loaded_lang;
 }
 
-- 
2.39.5 (Apple Git-154)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.