diff --git a/TeXmacs/progs/kernel/gui/menu-widget.scm b/TeXmacs/progs/kernel/gui/menu-widget.scm index 2cc636f16f..65de54048a 100644 --- a/TeXmacs/progs/kernel/gui/menu-widget.scm +++ b/TeXmacs/progs/kernel/gui/menu-widget.scm @@ -1244,14 +1244,57 @@ ;; Menu expansion ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(define menu-expand-link-cache (make-ahash-table)) + +(define (static-menu-link? name) + "Menus whose expanded result never changes at runtime." + (in? name + '(style-menu add-package-menu + remove-package-menu + toggle-package-menu + basic-theme-menu + document-page-size-menu + document-language-menu + document-short-font-menu + document-font-base-size-menu + page-rendering-menu + page-layout-menu + document-columns-menu + print-menu-inline + new-file-menu + load-menu + save-menu + close-menu + cite-texmacs-menu + cite-texmacs-related-menu + color-menu + document-encryption-menu + document-columns-menu) + ) ;in? +) ;define + (define (menu-expand-link p) "Expand menu link @p." - (with linked ((eval (cadr p))) (if linked (menu-expand linked) p)) + (let* ((name (cadr p)) + (cached (and (static-menu-link? name) (ahash-ref menu-expand-link-cache name))) + ) ; + (if cached + cached + (let* ((linked ((eval name))) (result (if linked (menu-expand linked) p))) + (when (and (static-menu-link? name) linked) + (ahash-set! menu-expand-link-cache name result) + ) ;when + result + ) ;let* + ) ;if + ) ;let* ) ;define (define (menu-expand-dynamic p) "Expand menu link @p." - (with dyn (eval (cadr p)) (if dyn (menu-expand dyn) p)) + (let* ((dyn (eval (cadr p))) (result (if dyn (menu-expand dyn) p))) + result + ) ;let* ) ;define (define (menu-expand-resize p) @@ -1367,9 +1410,16 @@ `(toggle ,(replace-procedures (cadr p)) ,((caddr p))) ) ;define +(define menu-expand-count 0) + (define (menu-expand-list l) "Expand links and conditional menus in list of menus @l." - (map menu-expand l) + (map (lambda (item) + (set! menu-expand-count (+ menu-expand-count 1)) + (menu-expand item) + ) ;lambda + l + ) ;map ) ;define (define must-eval-list '(input enum choice filtered-choice toggle)) diff --git a/devel/0146.md b/devel/0146.md new file mode 100644 index 0000000000..ec2d1e0c16 --- /dev/null +++ b/devel/0146.md @@ -0,0 +1,45 @@ +# [0146] 优化新建空白文档时焦点图标工具栏的性能 + +## 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 + +## 任务相关的代码文件 +- `src/Edit/Interface/edit_interface.cpp` - `update_menus()` 和 `resume()` 中调用 `menu_icons(2, ...)` +- `src/Data/Document/new_style.cpp` - `get_style_menu()` 等 style/package 菜单缓存 +- `TeXmacs/progs/generic/generic-menu.scm` - `texmacs-focus-icons`、`standard-focus-icons` 定义 +- `TeXmacs/progs/generic/document-menu.scm` - `focus-style-icons` 定义 +- `TeXmacs/progs/kernel/gui/menu-widget.scm` - `menu-expand` 展开逻辑及静态菜单缓存 +- `TeXmacs/progs/texmacs/menus/main-menu.scm` - `style-menu` 等函数定义 + +## 如何测试 + +### 非确定性测试(性能验证) +1. 启动 Mogan +2. 使用 `Ctrl+N` 或菜单新建空白文档 +3. 观察新建文档的响应速度 + +## 如何提交 + +```bash +xmake b stem +``` + +## What + +优化新建空白文档时,焦点图标工具栏(focus icon toolbar)的 expand 过程的性能。 + +## Why + +新建空白文档时,焦点图标工具栏的刷新存在性能瓶颈,冷启动 `menu-expand 2` 耗时约 60~80ms: + +1. `resume()` 在视图激活时调用 `menu_icons(2, "(horizontal (link texmacs-focus-icons))")` +2. `texmacs-focus-icons` 调用 `(standard-focus-icons (focus-tree))`,该菜单包含大量动态内容 +3. 其中的 `(link style-menu)` 触发 `get_style_menu()`,每次都要 `descendance` 遍历样式目录树 + `compute_style_menu` 构建菜单字符串 + `eval` 解析 +4. scheme 层 `menu-expand` 对 `(assuming ...)`、`(=> ...)`、`(for ...)` 等的递归展开也有显著开销 + +## How + +1. **C++ 层 static cache**:为 `get_style_menu()`、`get_add_package_menu()`、`get_remove_package_menu()`、`get_toggle_package_menu()` 添加 `static object cache`,首次计算后缓存结果,后续调用直接返回 +2. **scheme 层静态菜单缓存**:在 `menu-expand-link` 中通过 `static-menu-link?` 识别不依赖运行时状态的菜单函数(如 `style-menu`、`add-package-menu` 等),用 `menu-expand-link-cache`(ahash-table)缓存展开结果,按函数符号名作为 key + +当前状态:C++ static cache 和 scheme 静态菜单缓存已生效,`update_menus` 阶段 `menu-expand 2` 从约 30ms 降至约 10ms。冷启动首次展开约 60ms 的排查仍在进行中。 diff --git a/src/Data/Document/new_style.cpp b/src/Data/Document/new_style.cpp index 246e4d7f49..950c15bf27 100644 --- a/src/Data/Document/new_style.cpp +++ b/src/Data/Document/new_style.cpp @@ -58,6 +58,8 @@ init_style_data () { extern hashmap style_tree_cache; hashmap hidden_packages (false); +static hashmap hidden_package_set (false); +static bool hidden_package_set_initialized= false; static url resolve_local_style (string style_name) { @@ -172,10 +174,14 @@ cache_file_name (tree t) { return lolly::hash::md5_hexdigest (tmp) * ".scm"; } +void ensure_hidden_package_set (); + void style_invalidate_cache () { - style_tree_cache= hashmap (); - hidden_packages = hashmap (false); + style_tree_cache = hashmap (); + hidden_packages = hashmap (false); + hidden_package_set = hashmap (false); + hidden_package_set_initialized= false; if (sd != NULL) { tm_delete (sd); sd= NULL; @@ -359,34 +365,45 @@ ignore_dir (string dir) { (dir == "Standard") || (dir == "Test") || (dir == "Themes"); } -static bool -hidden_package (url u, string name, bool hidden) { - if (is_or (u)) - return hidden_package (u[1], name, hidden) || - hidden_package (u[2], name, hidden); +static void +collect_hidden_packages (url u, bool hidden, hashmap& pkgs) { + if (is_or (u)) { + collect_hidden_packages (u[1], hidden, pkgs); + collect_hidden_packages (u[2], hidden, pkgs); + return; + } if (is_concat (u)) { string dir= upcase_first (as_string (u[1])); - if (dir == "CVS" || dir == ".svn") return false; - return hidden_package (u[2], name, hidden || ignore_dir (dir)); + if (dir == "CVS" || dir == ".svn") return; + collect_hidden_packages (u[2], hidden || ignore_dir (dir), pkgs); + return; } if (hidden && is_atomic (u)) { string l= as_string (u); if (ends (l, ".ts")) l= l (0, N (l) - 3); else if (ends (l, ".hook")) l= l (0, N (l) - 5); - else return false; - return name == l; + else return; + pkgs (l)= true; + } +} + +static url get_package_root (); + +void +ensure_hidden_package_set () { + if (!hidden_package_set_initialized) { + bench_start ("hidden_package_init"); + collect_hidden_packages (get_package_root (), false, hidden_package_set); + hidden_package_set_initialized= true; + bench_end ("hidden_package_init"); } - return false; } bool hidden_package (string name) { if (name == "std-latex") return false; - if (!hidden_packages->contains (name)) { - url pck_u = descendance ("$TEXMACS_PACKAGE_ROOT"); - hidden_packages (name)= hidden_package (pck_u, name, false); - } - return hidden_packages[name]; + ensure_hidden_package_set (); + return hidden_package_set->contains (name); } static string @@ -426,30 +443,48 @@ compute_style_menu (url u, int kind) { return ""; } +static url +get_package_root () { + static url pck_u= descendance ("$TEXMACS_PACKAGE_ROOT"); + return pck_u; +} + object get_style_menu () { + static object cache; + if (cache != null_object ()) return cache; url sty_u= descendance ("$TEXMACS_STYLE_ROOT"); string sty = compute_style_menu (sty_u, 0); - return eval ("(menu-dynamic " * sty * ")"); + cache = eval ("(menu-dynamic " * sty * ")"); + return cache; } object get_add_package_menu () { - url pck_u= descendance ("$TEXMACS_PACKAGE_ROOT"); + static object cache; + if (cache != null_object ()) return cache; + url pck_u= get_package_root (); string pck = compute_style_menu (pck_u, 1); - return eval ("(menu-dynamic " * pck * ")"); + cache = eval ("(menu-dynamic " * pck * ")"); + return cache; } object get_remove_package_menu () { - url pck_u= descendance ("$TEXMACS_PACKAGE_ROOT"); + static object cache; + if (cache != null_object ()) return cache; + url pck_u= get_package_root (); string pck = compute_style_menu (pck_u, 2); - return eval ("(menu-dynamic " * pck * ")"); + cache = eval ("(menu-dynamic " * pck * ")"); + return cache; } object get_toggle_package_menu () { - url pck_u= descendance ("$TEXMACS_PACKAGE_ROOT"); + static object cache; + if (cache != null_object ()) return cache; + url pck_u= get_package_root (); string pck = compute_style_menu (pck_u, 3); - return eval ("(menu-dynamic " * pck * ")"); + cache = eval ("(menu-dynamic " * pck * ")"); + return cache; } diff --git a/src/Data/Document/new_style.hpp b/src/Data/Document/new_style.hpp index ed85a50f23..8c52f42c16 100644 --- a/src/Data/Document/new_style.hpp +++ b/src/Data/Document/new_style.hpp @@ -33,6 +33,7 @@ tree get_document_preamble (tree t); drd_info get_document_drd (tree doc); object get_style_menu (); +void ensure_hidden_package_set (); bool hidden_package (string name); object get_add_package_menu (); object get_remove_package_menu (); diff --git a/src/Edit/Interface/edit_interface.cpp b/src/Edit/Interface/edit_interface.cpp index 340d7a7bba..5f152d8ec4 100644 --- a/src/Edit/Interface/edit_interface.cpp +++ b/src/Edit/Interface/edit_interface.cpp @@ -111,6 +111,7 @@ edit_interface_rep::suspend () { void edit_interface_rep::resume () { // cout << "Resume " << buf->buf->name << LF; + bench_start ("resume"); got_focus= true; SERVER (menu_main ("(horizontal (link texmacs-menu))")); SERVER (menu_icons (0, "(horizontal (link texmacs-main-icons))")); @@ -145,6 +146,7 @@ edit_interface_rep::resume () { // after a bugfix by Massimiliano during summer 2016 eval ("(delayed (:idle 1) (refresh-window))"); #endif + bench_end ("resume"); } void @@ -810,6 +812,7 @@ edit_interface_rep::change_time () { void edit_interface_rep::update_menus () { + bench_start ("update_menus"); SERVER (menu_main ("(horizontal (link texmacs-menu))")); SERVER (menu_icons (0, "(horizontal (link texmacs-main-icons))")); SERVER (menu_icons (1, "(horizontal (link texmacs-mode-icons))")); @@ -840,6 +843,7 @@ edit_interface_rep::update_menus () { cache_memorize (); last_update= last_change; save_user_preferences (); + bench_end ("update_menus"); } int diff --git a/src/System/Boot/init_texmacs.cpp b/src/System/Boot/init_texmacs.cpp index 767792d689..aef1d38e10 100644 --- a/src/System/Boot/init_texmacs.cpp +++ b/src/System/Boot/init_texmacs.cpp @@ -18,6 +18,7 @@ #include "merge_sort.hpp" #include "moebius/tree_label.hpp" #include "new_buffer.hpp" +#include "new_style.hpp" #include "new_window.hpp" #include "preferences.hpp" #include "server.hpp" @@ -969,6 +970,7 @@ TeXmacs_main (int argc, char** argv) { #endif if (N (extra_init_cmd) > 0) exec_delayed (scheme_cmd (extra_init_cmd)); + ensure_hidden_package_set (); gui_start_loop (); if (DEBUG_STD) debug_boot << "Stopping server...\n";