Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 158 additions & 28 deletions TeXmacs/tests/1150.scm
Original file line number Diff line number Diff line change
@@ -1,54 +1,184 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; MODULE : 1150.scm
;; DESCRIPTION : 基准测试:在表格中连续插入新行的性能
;; DESCRIPTION : GUI 基准:在新建标签页的表格中连续插入新行的性能
;; COPYRIGHT : (C) 2026 Mogan STEM
;;
;; PURPOSE
;; 测量"在表格里连续向下插入 N 行"的总耗时与单行平均耗时,
;; 作为后续表格插入路径优化的对比基线。
;; 测量"在表格里连续向下插入 N 行"的总耗时,配合 C++ 侧 bench 埋点
;; (table_insert_row / table_insert / table_correct_block_content /
;; table_resize_notify)定位热点。
;;
;; 实现要点:
;; 实现要点(参考 1145.scm 的 GUI 异步链模式):
;; - `(new-document)` 在界面里新建一个标签页(遵循 window-per-buffer? 偏好)。
;; 紧接 `(set-main-style "generic")` 切到 generic 样式——new-document
;; 默认开无样式文档,与用户实际场景不一致。
;; - `(make 'tabular)` 新建 1×1 表格并定位光标到首个 cell,
;; 与「插入 → 表格」菜单同源。
;; - `(table-insert-row #t)` 是 kbd-enter / 菜单"Row below"走的路径。
;; - 每行插入之间不插入 kbd 等待或排版抖动;insert 内部已驱动必要的
;; 排版增量更新,测量的是真实用户体感的"按一下回车"链路耗时。
;; - 用 `exec-delayed-pause` + `run-chain` 串异步链,每步间隔 step-delay-ms
;; 让事件循环真正驱动 GUI(typeset + idle update_menus 才会跑)。
;; 必须真实 GUI 跑;exec-delayed-at 单次调度事件循环不真正驱动,
;; 本质还是 headless。
;; - 每步插 rows-per-step 行(默认 1 行),步间让事件循环空转 +
;; `(update-menus)` 同步触发 C++ 侧刷新。
;; - 在 checkpoint-rows / total-rows 处各打印一次 bench dump + 累积
;; 插入耗时,一次运行直接对比两个规模的增长曲线。
;;
;; bench 输出:链尾调用 `(bench-print-all)` dump C++ 侧所有累积 task。
;;
;; USAGE
;; xmake b stem
;; xmake r 1150
;; MOGAN_TEST_GUI=1 xmake r 1150
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(texmacs-module (texmacs tests 1150))

(define insert-count 1000)

(define (bench-insert-rows n)
(make 'tabular)
(let ((start (texmacs-time)))
(let loop ((i 0))
(when (< i n)
(table-insert-row #t)
(loop (+ i 1))
) ;when
) ;let
(let* ((elapsed (- (texmacs-time) start))
(per-row (if (zero? n) 0 (/ elapsed n)))
) ;
(display "[1150] insert ")
;; 步骤间隔:给 typeset + idle update_menus 足够时间(idle >= 1/60s 才触发)。

(define step-delay-ms 1500)

;; 先用小规模探明热点:跑到 checkpoint-rows 打印一次 bench dump + 总耗时,
;; 再跑到 total-rows 打印第二次。一次运行直接对比两个规模的增长曲线。

(define checkpoint-rows 50)

(define total-rows 100)

(define rows-per-step 1)

;; 每行的列数。建表后先扩到 num-cols 列再开始插行——真实表格很少是单列,
;; 多列场景下 table_correct_block_content 的 O(N×M) 开销更显著。

(define num-cols 10)

;; 记录每步开始的时间,链尾累加得到总插入耗时(不含步间 sleep)。

(define step-start-time 0)

(define total-insert-ms 0)

(define (log-step label)
(display "[1150-step] ")
(display label)
(newline)
) ;define

;; run-chain 复刻 1145.scm 的 exec-delayed-pause 模式:每步 lambda 返回剩余
;; 毫秒表示继续等待,返回 #t 表示完成。

(define (run-chain steps on-done)
(if (null? steps)
(on-done)
(exec-delayed-pause (let ((start (texmacs-time)))
(lambda ()
(let ((left (- (+ start step-delay-ms) (texmacs-time))))
(if (> left 0)
left
(begin
(log-step (caar steps))
((cdar steps))
;; 同步触发 C++ update_menus,不依赖 idle/焦点
(update-menus)
(run-chain (cdr steps) on-done)
#t
) ;begin
) ;if
) ;let
) ;lambda
) ;let
) ;exec-delayed-pause
) ;if
) ;define

;; 当前已插入行数(跨步累积)

(define rows-so-far 0)

;; 单步:同步插 rows-per-step 行。开头记 start、结尾累加 elapsed 到 total。

(define (make-insert-step from-rows target-rows)
(cons (string-append "insert row "
(number->string (+ from-rows 1))
" → "
(number->string target-rows)
) ;string-append
(lambda ()
(set! step-start-time (texmacs-time))
(let loop
((k 0))
(when (< k rows-per-step)
(table-insert-row #t)
(set! rows-so-far (+ rows-so-far 1))
(loop (+ k 1))
) ;when
) ;let
(set! total-insert-ms (+ total-insert-ms (- (texmacs-time) step-start-time)))
) ;lambda
) ;cons
) ;define

;; 在指定行数处插入一次 bench dump 步骤(不 reset,累积继续)

(define (make-checkpoint-step n)
(cons (string-append "checkpoint @ " (number->string n) " rows")
(lambda ()
(display "[1150] @ ")
(display n)
(display " rows: total=")
(display elapsed)
(display " ms, per-row=")
(display per-row)
(display " rows: cumulative insert=")
(display total-insert-ms)
(display " ms")
(newline)
) ;let*
(bench-print-all)
) ;lambda
) ;cons
) ;define

;; 生成 [from..n] 行的插入步序列(不依赖全局 rows-so-far——它在 step 执行后才更新)

(define (insert-steps-up-to from n)
(let loop
((i from) (acc '()))
(if (>= i n)
(reverse acc)
(loop (+ i rows-per-step) (cons (make-insert-step i (+ i rows-per-step)) acc))
) ;if
) ;let
) ;define

(tm-define (test_1150)
(bench-insert-rows insert-count)
;; 重置累积
(set! total-insert-ms 0)
(set! rows-so-far 0)
;; 链头四步:新建标签页 + 切 generic 样式 + 建表 + 扩到 num-cols 列;
;; 之后插到 checkpoint-rows → 打印 checkpoint → 插到 total-rows → checkpoint → quit。
(let* ((head (list (cons "new-document" (lambda () (new-document)))
(cons "set style generic" (lambda () (set-main-style "generic")))
(cons "make tabular" (lambda () (make 'tabular)))
(cons (string-append "expand to " (number->string num-cols) " cols")
(lambda ()
(let loop
((c 1))
(when (< c num-cols)
(table-insert-column #t)
(loop (+ c 1))
) ;when
) ;let
) ;lambda
) ;cons
) ;list
) ;head
(steps (append head
(insert-steps-up-to 0 checkpoint-rows)
(list (make-checkpoint-step checkpoint-rows))
(insert-steps-up-to checkpoint-rows total-rows)
(list (make-checkpoint-step total-rows))
) ;append
) ;steps
(on-done (lambda () (display "[1150-step] done, quit") (newline) (quit-TeXmacs))
) ;on-done
) ;
(run-chain steps on-done)
) ;let*
) ;tm-define
77 changes: 67 additions & 10 deletions devel/1150.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,84 @@

## 背景

排查"表格里连续插入新行"的响应耗时。需要一个稳定的可重跑基准
便于后续优化前后对比
排查"表格里连续插入新行"的响应耗时。先用 GUI 基准定位热点
再做局部优化

## What

新增 `TeXmacs/tests/1150.scm`,在 headless 编辑器里:
新增 `TeXmacs/tests/1150.scm`,在 GUI 编辑器里:

1. `(make 'tabular)` 新建一个 1×1 表格
2. 连续调用 `(table-insert-row #t)` 插入 N 行(默认 N=1000)
3. 用 `texmacs-time` 计时,输出总耗时与每行平均耗时
1. `(new-document)` 在界面里新建一个标签页
2. `(set-main-style "generic")` 切到 generic 样式(new-document 默认无样式)
3. `(make 'tabular)` 新建 1×1 表格,扩到 10 列
4. 用 `exec-delayed-pause` + `run-chain` 异步链逐行插入
5. 在 `checkpoint-rows` / `total-rows` 处各打印一次 bench dump + 累积耗时

实现风格参考 `TeXmacs/tests/1144.scm`(纯 headless 基准,无 GUI、
无 `exec-delayed-at` 异步链)。
实现风格参考 `TeXmacs/tests/1145.scm`(`exec-delayed-pause` + `update-menus`
驱动真实事件循环,避免退化为 headless)。

## 表格插入行 C++ 链路

入口 `(table-insert-row #t)` 经 glue(`src/Scheme/Glue/glue_editor.lua`)
到 `edit_table_rep::table_insert_row`(`src/Edit/Modify/edit_table.cpp`),
内部分 5 步:

| 步 | 方法 | 作用 |
|---|---|---|
| 1 | `table_insert` | 核心 tree 改写:行插入 / 列插入循环 / CWITH 节点坐标重写 |
| 2 | `table_go_to` | 光标定位 |
| 3 | `table_correct_block_content` | 遍历 cell,按 block/hyphen 格式增删 DOCUMENT wrap |
| 4 | `table_resize_notify` | `call("table-resize-notify", ...)` 回 scheme |
| 5 | (底层)`edit_insert` → `notify_insert` → typesetter invalidation | 每次 tree mutation 的固定成本 |

## bench 埋点(C++ 侧)

`src/Edit/Modify/edit_table.cpp` 加了 `tm_debug.hpp` include 和若干
`bench_start`/`bench_cumul` 埋点:

- `table_insert_row`(顶层 + 5 个子段 `:search`/`:insert`/`:go_to`/`:correct_block`/`:resize_notify`)
- `table_insert`(3 个子段 `:row`/`:col`/`:cwith`)
- `table_correct_block_content`(整体一层)
- `table_resize_notify`(整体一层)

用 `bench_cumul`(不 reset、不每次打印)累积;测试链尾用
`(bench-print-all)` 一次性 dump。

## 性能优化(GUI 模式,10 列,generic 样式)

baseline @ 100 rows:1531 ms。bench 分布显示两个真热点:

- `table_correct_block_content` 666 ms(44%)—— 每插一行扫所有 cell
- `table_go_to` 646 ms(42%)—— 内部夹了一个 dead `table_bound` 调用

三处优化后 @ 100 rows:**657 ms(提速 57%)**;@ 50 rows:314 ms(提速 45%)。

| 优化 | 文件:行 | 效果 |
|---|---|---|
| `table_correct_block_content` 加范围化重载,`table_insert_row`/`table_insert_column` 只扫新行/列 | `edit_table.cpp` `table_correct_block_content(path,int,int,int,int)` | correct_block 666→3 ms |
| 删掉 `table_go_to` 里 dead `table_bound` 调用(结果立即丢弃,每次跑 O(N×M)) | `edit_table.cpp` `table_go_to` | go_to 646→201 ms |
| `empty_table`/`empty_row` 加 wrap-aware 重载,`table_insert` 在构造新行/列时一次性预包装 DOCUMENT,省掉后续每个 cell 一次 `insert_node` | `edit_table.cpp` `empty_row(int,bool)` / `empty_table(int,int,bool)` / `table_insert` | correct_block 3 ms 持续,从源头避免 10 次 mutation |

### 当前剩余热点(@ 100 rows 优化后)

- `table_insert_row:go_to` 201 ms (31%) — `go_to_border` 走 `edit_cursor_rep::go_to` 同步触发 `notify_change(THE_CURSOR)` + `set_user_active`,是系统级成本,深挖风险大
- `table_resize_notify` 197 ms (30%) — scheme callback 路径
- `table_insert_row:insert` 38 ms (6%) — 实际 tree mutation

## How to run

```bash
xmake b stem
xmake r 1150
MOGAN_TEST_GUI=1 xmake r 1150
```

## 涉及文件

- `TeXmacs/tests/1150.scm`(新增)
- `TeXmacs/tests/1150.scm`(新增;GUI 异步链 + checkpoint 对比)
- `src/Edit/Modify/edit_table.cpp`(bench 埋点 + 三处优化)
- `src/Edit/Modify/edit_table.hpp`(`table_correct_block_content` 范围化重载)

## 验证

- `xmake r edit_table_test` —— 14 个表格单测全过
- `xmake r table_performance_test` —— 8 个表格性能/正确性测试全过(含 `test_cell_hyphen_wrapping`,验证 pre-wrap 不破坏 wrap 行为)
Loading
Loading