diff --git a/.gitignore b/.gitignore
index 0a17e0b..65cdb76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -200,3 +200,4 @@ FakesAssemblies/
_bin/
_Nuget/NuGet.exe
snks/*.snk
+BenchmarkDotNet.Artifacts/
diff --git a/benchmark/JiebaNet.ForkBench/JiebaNet.ForkBench.csproj b/benchmark/JiebaNet.ForkBench/JiebaNet.ForkBench.csproj
new file mode 100644
index 0000000..f8c63a4
--- /dev/null
+++ b/benchmark/JiebaNet.ForkBench/JiebaNet.ForkBench.csproj
@@ -0,0 +1,31 @@
+
+
+
+ Exe
+ net10.0
+ JiebaNet.Benchmark
+ JiebaNet.ForkBench
+ disable
+ disable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/benchmark/JiebaNet.UpstreamBench/JiebaNet.UpstreamBench.csproj b/benchmark/JiebaNet.UpstreamBench/JiebaNet.UpstreamBench.csproj
new file mode 100644
index 0000000..e2c14fb
--- /dev/null
+++ b/benchmark/JiebaNet.UpstreamBench/JiebaNet.UpstreamBench.csproj
@@ -0,0 +1,33 @@
+
+
+
+ Exe
+ net10.0
+ JiebaNet.Benchmark
+ JiebaNet.UpstreamBench
+ disable
+ disable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/benchmark/README.md b/benchmark/README.md
new file mode 100644
index 0000000..b25db14
--- /dev/null
+++ b/benchmark/README.md
@@ -0,0 +1,28 @@
+# Benchmark
+
+对比本仓库(fork)与 NuGet 上的上游 [jieba.NET 0.42.2](https://www.nuget.org/packages/jieba.NET) 的性能。
+
+## 布局
+
+- `Shared/BenchSuite.cs` — 共享的 BenchmarkDotNet 套件(分词精确/全模式/HMM/搜索引擎、词性标注、关键词提取,共 12 个场景 + 冷启动),分别链接进两个项目。两个库的类型名完全相同,无法在单项目中同时引用,因此采用双项目布局。
+- `JiebaNet.ForkBench` — 引用本仓库源码。
+- `JiebaNet.UpstreamBench` — 引用 NuGet 包 `jieba.NET 0.42.2`,并自动把仓库 `src/Segmenter/Resources` 复制到输出目录(上游从磁盘加载词典),保证两边数据一致。
+
+## 运行
+
+```powershell
+cd benchmark
+.\run.ps1 # 双方全场景 + 对比表
+.\run.ps1 -Side fork # 只跑本仓库
+.\run.ps1 -Filter "*medium*" # 只跑部分场景(BDN 过滤器)
+```
+
+脚本会:串行执行双方(避免 CPU 争抢)→ 打印 BDN 原始表格 → 各测 3 次冷启动(词典/模型加载)→ 输出按场景合并的对比表(`upstream / fork` 比值 > 1 表示 fork 更快)。
+
+原始结果(CSV/Markdown)在各项目的 `BenchmarkDotNet.Artifacts/results/` 目录。
+
+## 说明
+
+- 套件使用 `ShortRunJob`(3 次预热 + 3 次迭代)+ `MemoryDiagnoser`:适合优化前后的方向性对比;如需发布级性能数据,把 `BenchSuite.cs` 上的 `[ShortRunJob]` 移除即可使用默认 Job。
+- 新增场景:在 `Shared/BenchSuite.cs` 加 `[Benchmark]` 方法即可,两个项目自动共享。
+- `Program.cs cold` 模式用于测量真正的冷启动(BDN 的预热会把词典加载排除在统计之外)。
diff --git a/benchmark/Shared/BenchSuite.cs b/benchmark/Shared/BenchSuite.cs
new file mode 100644
index 0000000..40ba1db
--- /dev/null
+++ b/benchmark/Shared/BenchSuite.cs
@@ -0,0 +1,90 @@
+using System;
+using System.IO;
+using System.Linq;
+using BenchmarkDotNet.Attributes;
+using JiebaNet.Analyser;
+using JiebaNet.Segmenter;
+using JiebaNet.Segmenter.PosSeg;
+
+namespace JiebaNet.Benchmark
+{
+ // Runs in its own (fresh) process: measures true cold start, including
+ // dictionary/model loading (embedded streams in this fork, files on disk upstream).
+ [ShortRunJob]
+ [MemoryDiagnoser]
+ public class InitBench
+ {
+ [Benchmark(Description = "Cold init + first cut")]
+ public string InitAndFirstCut()
+ {
+ var seg = new JiebaSegmenter();
+ return string.Join("/", seg.Cut("我来到北京清华大学"));
+ }
+ }
+
+ [ShortRunJob]
+ [MemoryDiagnoser]
+ public class BenchSuite
+ {
+ private const string ShortText =
+ "我来到北京清华大学,打算去附近的餐馆吃晚饭,然后再回实验室继续写代码。";
+
+ private string _medium;
+ private string _large;
+ private JiebaSegmenter _seg;
+ private PosSegmenter _pos;
+ private TfidfExtractor _tfidf;
+ private TextRankExtractor _textrank;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ var input = Path.Combine(AppContext.BaseDirectory, "input");
+ _medium = File.ReadAllText(Path.Combine(input, "article_social.txt"));
+ _large = File.ReadAllText(Path.Combine(input, "weicheng.txt"));
+
+ _seg = new JiebaSegmenter();
+ _seg.Cut("热身一下"); // trigger dictionary/model loading outside the timed region
+ _pos = new PosSegmenter(_seg);
+ _tfidf = new TfidfExtractor(_seg);
+ _textrank = new TextRankExtractor();
+ _textrank.ExtractTags("热身一下");
+ }
+
+ [Benchmark(Description = "Cut accurate+HMM short")]
+ public int CutAccurateHmmShort() => _seg.Cut(ShortText).Count();
+
+ [Benchmark(Description = "Cut accurate no-HMM short")]
+ public int CutAccurateNoHmmShort() => _seg.Cut(ShortText, hmm: false).Count();
+
+ [Benchmark(Description = "CutAll short")]
+ public int CutAllShort() => _seg.Cut(ShortText, cutAll: true).Count();
+
+ [Benchmark(Description = "Cut accurate+HMM medium")]
+ public int CutAccurateHmmMedium() => _seg.Cut(_medium).Count();
+
+ [Benchmark(Description = "Cut accurate no-HMM medium")]
+ public int CutAccurateNoHmmMedium() => _seg.Cut(_medium, hmm: false).Count();
+
+ [Benchmark(Description = "CutAll medium")]
+ public int CutAllMedium() => _seg.Cut(_medium, cutAll: true).Count();
+
+ [Benchmark(Description = "CutForSearch medium")]
+ public int CutForSearchMedium() => _seg.CutForSearch(_medium).Count();
+
+ [Benchmark(Description = "POS cut medium")]
+ public int PosCutMedium() => _pos.Cut(_medium).Count();
+
+ [Benchmark(Description = "TF-IDF keywords medium")]
+ public int TfidfMedium() => _tfidf.ExtractTags(_medium).Count();
+
+ [Benchmark(Description = "TextRank keywords medium")]
+ public int TextRankMedium() => _textrank.ExtractTags(_medium).Count();
+
+ [Benchmark(Description = "Cut accurate+HMM large (novel)")]
+ public int CutAccurateHmmLarge() => _seg.Cut(_large).Count();
+
+ [Benchmark(Description = "CutAll large (novel)")]
+ public int CutAllLarge() => _seg.Cut(_large, cutAll: true).Count();
+ }
+}
diff --git a/benchmark/Shared/Program.cs b/benchmark/Shared/Program.cs
new file mode 100644
index 0000000..921336b
--- /dev/null
+++ b/benchmark/Shared/Program.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Diagnostics;
+using System.Linq;
+using BenchmarkDotNet.Running;
+using JiebaNet.Benchmark;
+using JiebaNet.Segmenter;
+
+if (args.Length > 0 && args[0] == "cold")
+{
+ // One-shot cold start measurement: dictionary/model loading included.
+ // Each `dotnet run` invocation is a fresh process.
+ var sw = Stopwatch.StartNew();
+ var seg = new JiebaSegmenter();
+ var tokens = seg.Cut("我来到北京清华大学").Count();
+ sw.Stop();
+ Console.WriteLine($"COLD_INIT_MS={sw.ElapsedMilliseconds} tokens={tokens}");
+ return;
+}
+
+BenchmarkSwitcher.FromAssembly(typeof(BenchSuite).Assembly).Run(args);
diff --git a/benchmark/run.ps1 b/benchmark/run.ps1
new file mode 100644
index 0000000..8592d5e
--- /dev/null
+++ b/benchmark/run.ps1
@@ -0,0 +1,111 @@
+#Requires -Version 5.1
+<#
+.SYNOPSIS
+ Run the fork-vs-upstream jieba.NET benchmark and print a combined comparison.
+
+.EXAMPLE
+ .\run.ps1 # build + run both sides + comparison table
+ .\run.ps1 -Side fork # only this repo's library
+ .\run.ps1 -Filter "*medium*" # BDN filter for a scenario subset
+#>
+param(
+ [ValidateSet("both", "fork", "upstream")]
+ [string]$Side = "both",
+ [string]$Filter = "*"
+)
+
+$ErrorActionPreference = "Stop"
+$benchmarkDir = $PSScriptRoot
+
+function Invoke-Bench([string]$project, [string]$label)
+{
+ Write-Host ""
+ Write-Host "===== Building $label =====" -ForegroundColor Cyan
+ dotnet build "$benchmarkDir\$project\$project.csproj" -c Release
+ if ($LASTEXITCODE -ne 0) { throw "build failed: $project" }
+
+ Write-Host ""
+ Write-Host "===== Running $label (a few minutes) =====" -ForegroundColor Cyan
+ dotnet run --project "$benchmarkDir\$project\$project.csproj" -c Release --no-build -- --filter "$Filter"
+ if ($LASTEXITCODE -ne 0) { throw "benchmark failed: $project" }
+
+ Write-Host ""
+ Write-Host "===== $label cold start (3 fresh processes) =====" -ForegroundColor Cyan
+ 1..3 | ForEach-Object {
+ $output = dotnet "$benchmarkDir\$project\bin\Release\net10.0\$project.dll" cold | Select-String "COLD_INIT_MS=(\d+)"
+ if ($output.Matches.Success)
+ {
+ "cold #{0}: {1} ms" -f $_, $output.Matches[0].Groups[1].Value
+ }
+ }
+}
+
+function ConvertTo-Milliseconds([string]$mean)
+{
+ if ([string]::IsNullOrWhiteSpace($mean)) { return $null }
+ if ($mean -match "^([\d.,]+)\s*(\S+)$")
+ {
+ $value = [double]($Matches[1] -replace ",", "")
+ $unit = $Matches[2]
+ $micro = [string][char]0x03BC + "s"
+ switch ($unit)
+ {
+ "ns" { return $value / 1e6 }
+ "us" { return $value / 1e3 }
+ $micro { return $value / 1e3 }
+ "ms" { return $value }
+ "s" { return $value * 1e3 }
+ default { return $value }
+ }
+ }
+ return $null
+}
+
+if ($Side -in @("both", "fork")) { Invoke-Bench "JiebaNet.ForkBench" "fork (this repo)" }
+if ($Side -in @("both", "upstream")) { Invoke-Bench "JiebaNet.UpstreamBench" "upstream (NuGet jieba.NET 0.42.2)" }
+
+Write-Host ""
+Write-Host "===== Raw reports =====" -ForegroundColor Cyan
+foreach ($project in @("JiebaNet.ForkBench", "JiebaNet.UpstreamBench"))
+{
+ $report = Get-ChildItem "$benchmarkDir\$project\BenchmarkDotNet.Artifacts\results" -Filter "*BenchSuite-report-github.md" -ErrorAction SilentlyContinue | Select-Object -First 1
+ if ($report)
+ {
+ Write-Host "--- $project ---"
+ Get-Content $report.FullName | Out-Host
+ }
+}
+
+$forkCsv = Get-ChildItem "$benchmarkDir\JiebaNet.ForkBench\BenchmarkDotNet.Artifacts\results" -Filter "*BenchSuite-report.csv" -ErrorAction SilentlyContinue | Select-Object -First 1
+$upstreamCsv = Get-ChildItem "$benchmarkDir\JiebaNet.UpstreamBench\BenchmarkDotNet.Artifacts\results" -Filter "*BenchSuite-report.csv" -ErrorAction SilentlyContinue | Select-Object -First 1
+
+if ($forkCsv -and $upstreamCsv)
+{
+ $fork = Import-Csv $forkCsv.FullName -Encoding UTF8
+ $upstream = Import-Csv $upstreamCsv.FullName -Encoding UTF8
+
+ $rows = foreach ($row in $fork)
+ {
+ $other = $upstream | Where-Object { $_.Method -eq $row.Method }
+ if ($null -eq $other) { continue }
+
+ $forkMs = ConvertTo-Milliseconds $row.Mean
+ $upstreamMs = ConvertTo-Milliseconds $other.Mean
+ [pscustomobject]@{
+ "Scenario" = $row.Method
+ "fork ms" = [math]::Round($forkMs, 3)
+ "upstream ms" = [math]::Round($upstreamMs, 3)
+ "upstream / fork" = if ($forkMs -gt 0) { [math]::Round($upstreamMs / $forkMs, 2) } else { $null }
+ "fork alloc" = $row.Allocated
+ "upstream alloc" = $other.Allocated
+ }
+ }
+
+ Write-Host ""
+ Write-Host "===== Comparison (BDN Mean; ratio > 1 means the fork is faster) =====" -ForegroundColor Green
+ $rows | Sort-Object "Scenario" | Format-Table -AutoSize | Out-Host
+}
+else
+{
+ Write-Host "Benchmark artifacts not found; run both sides to produce the comparison." -ForegroundColor Yellow
+}
diff --git a/jieba.NET.slnx b/jieba.NET.slnx
index 1eed459..89a4983 100644
--- a/jieba.NET.slnx
+++ b/jieba.NET.slnx
@@ -8,4 +8,8 @@
+
+
+
+