Skip to content

Tier 1+2 optimizations: arena index, array DP, span probing - #9

Merged
rabbitism merged 1 commit into
mainfrom
perf/tier1-tier2
Sep 8, 2026
Merged

rabbitism merged 1 commit into
mainfrom
perf/tier1-tier2

Conversation

@rabbitism

Copy link
Copy Markdown
Member

概述

落实此前排期的第一、二梯队优化。行为保持逐字节一致(123 个测试含逐字比对全过,NativeAOT 冒烟通过)。

变更

第一梯队(冷启动)

  • WordDictIndex:字符前缀索引改为 arena 分配——结构体节点存入大数组、以索引互指,子节点用兄弟链(低分支)+ 阈值升级字典(高分支),构建期不再有 55 万次逐节点堆分配。
  • LoadDict 改 Span 解析:ReadOnlySpan 切分 + int.TryParse(span),替代每行 Split(' ') 的数组与小字符串分配;坏行行为与原实现一致(无空格 Debug.Fail 跳过,坏词频 FormatException 中止加载)。

第二梯队(运行时)

  • GetDag 返回稠密 WordEnd[][](单个复用的 scratch list 填充),Calc 填充 int[]/double[] 路由数组——彻底移除每句 n 个 List + n+1 个 Pair 分配与字典开销。
  • HMM Viterbi 数组化:B/M/E/S 固定四状态的发射/转移/初始概率在加载时转为数组,动态规划用 double[n*4] + int[n*4] 平坦表——每字符零字典、零 Node 链分配;平局裁决(<= 取后者)与原实现逐一对齐。
  • CutForSearch/Tokenize:gram 探查改走 ContainsWord(ReadOnlySpan<char>),仅在命中时分配字符串。

Benchmark(i9-10900KF / .NET 10.0.9 / BDN ShortRun,vs 上游 0.42.2)

场景 本轮前 fork 本轮后 fork 上游 fork vs 上游
Cut 精确无 HMM medium 387 μs 296 μs 740 μs 2.50x
Cut 精确+HMM medium 516 μs 362 μs 847 μs 2.34x
CutForSearch medium 573 μs 428 μs 937 μs 2.19x
TF-IDF medium 694 μs 506 μs 1,046 μs 2.07x
词性标注 medium 4,583 μs 4,288 μs 6,477 μs 1.51x
Cut 精确+HMM 大文本 170.3 ms 132.3 ms 232.8 ms 1.76x
CutAll 大文本 140.5 ms 88.7 ms 144.4 ms 1.63x

全部 12 个场景 1.28x~2.89x;分配普遍下降 25%~67%(如 medium 精确+HMM 872→364 KB)。冷启动 ~680ms(arena+Span 解析较上轮 ~750ms 改善;剩余差距是支撑公开 API 的字符串 Trie,优化路径见代码注释)。

- WordDictionary: the character-level prefix index is now arena-
  allocated (struct nodes referenced by index, sibling-chain children
  upgrading to per-node dictionaries at high branching factors), and
  dict.txt is parsed with spans instead of Split/int.Parse.
- GetDag returns dense WordEnd[][] rows built through one reusable
  scratch list; Calc fills int[]/double[] route arrays instead of a
  Dictionary of Pair allocations.
- FinalSeg Viterbi: the B/M/E/S hidden states use flat double[]/int[]
  dynamic programming tables - no per-character dictionary or Node
  chain allocations.
- CutForSearch/Tokenize: gram probes go through ContainsWord span
  overloads, allocating only accepted grams.

Cold start improved from ~750ms to ~680ms (index arena costs less than
the previous node graph, span parsing is cheaper); upstream is ~480ms -
the remaining gap is the string Trie that still backs the public
surface (see WordDictionary).
@rabbitism

Copy link
Copy Markdown
Member Author
Scenario fork (ms) upstream (ms) upstream / fork fork alloc upstream alloc
Cut accurate no-HMM short 0.003 0.009 2.9 5.55 KB 15.18 KB
Cut accurate+HMM short 0.004 0.011 2.67 6.54 KB 18.21 KB
Cut accurate no-HMM medium 0.282 0.71 2.51 294.51 KB 859.3 KB
CutForSearch medium 0.395 0.98 2.48 402.31 KB 1111.2 KB
TF-IDF keywords medium 0.486 1.153 2.37 421.06 KB 1116.05 KB
Cut accurate+HMM medium 0.341 0.807 2.36 364.17 KB 1059.15 KB
Cut accurate+HMM large (novel) 114.914 238.986 2.08 62210.73 KB 185949.11 KB
CutAll short 0.003 0.005 1.9 4.95 KB 10.43 KB
CutAll medium 0.262 0.497 1.9 279.22 KB 580.16 KB
CutAll large (novel) 80.875 149.731 1.85 37144.45 KB 70972.64 KB
POS cut medium 4.014 6.551 1.63 1559.33 KB 2500.68 KB
TextRank keywords medium 5.867 8.242 1.4 1946.97 KB 2906.95 KB

@rabbitism
rabbitism merged commit 89c5010 into main Sep 8, 2026
5 checks passed
@rabbitism
rabbitism deleted the perf/tier1-tier2 branch September 8, 2026 02:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant