Repository navigation
research(core): 保留历史内存与 H10000 Search 成本 (#164) #176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // Diagnostic only (issue #164 remainder): retained-history memory and a single | ||
| // bounded Search at fixed history sizes on the production Screen/TextSearch. | ||
| // It reports what it measured and asserts nothing about performance targets. | ||
| #include "screen.hpp" | ||
| #include "terminal.hpp" | ||
| #include "text_document.hpp" | ||
| #include "text_search.hpp" | ||
| #include <windows.h> | ||
| #include <psapi.h> | ||
| #include <chrono> | ||
| #include <cstdint> | ||
| #include <iostream> | ||
| #include <string> | ||
|
|
||
| using namespace termforge; | ||
|
|
||
| namespace { | ||
| struct Usage { SIZE_T working = 0, peak_working = 0, private_bytes = 0; }; | ||
|
|
||
| Usage current_usage() { | ||
| using Fn = BOOL(WINAPI*)(HANDLE, PROCESS_MEMORY_COUNTERS_EX*, DWORD); | ||
| static const auto fn = reinterpret_cast<Fn>( | ||
| GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "K32GetProcessMemoryInfo")); | ||
| PROCESS_MEMORY_COUNTERS_EX counters{}; | ||
| counters.cb = sizeof(counters); | ||
| if (!fn || !fn(GetCurrentProcess(), &counters, sizeof(counters))) return {}; | ||
| return {counters.WorkingSetSize, counters.PeakWorkingSetSize, counters.PrivateUsage}; | ||
| } | ||
| long long micros(std::chrono::steady_clock::time_point from, std::chrono::steady_clock::time_point to) { | ||
| return std::chrono::duration_cast<std::chrono::microseconds>(to - from).count(); | ||
| } | ||
| } // namespace | ||
|
|
||
| int main(int argc, char** argv) { | ||
| size_t history = 0, lines = 0; | ||
| std::string query = "needle"; | ||
| for (int index = 1; index < argc; ++index) { | ||
| const std::string arg = argv[index]; | ||
| if (arg == "--history" && index + 1 < argc) history = static_cast<size_t>(std::stoul(argv[++index])); | ||
| else if (arg == "--lines" && index + 1 < argc) lines = static_cast<size_t>(std::stoul(argv[++index])); | ||
| else if (arg == "--query" && index + 1 < argc) query = argv[++index]; | ||
| else { std::cerr << "usage: history_memory_probe --history N --lines M [--query text]\n"; return 2; } | ||
| } | ||
| if (!history || history > 10000 || !lines) { std::cerr << "history 1..10000 and lines>0 required\n"; return 2; } | ||
|
|
||
| const Usage before = current_usage(); | ||
| Parser parser(24, 80, history); | ||
| // Fill the retained history: the live screen keeps 24 rows, the rest evict | ||
| // into the bounded history in document order. | ||
| const auto fill_begin = std::chrono::steady_clock::now(); | ||
| std::string batch; | ||
| batch.reserve(1 << 20); | ||
| for (size_t line = 0; line < lines; ++line) { | ||
| char prefix[16]; | ||
| std::snprintf(prefix, sizeof(prefix), "R%06zu ", line); | ||
| std::string text = prefix; | ||
| // One hit every 1000 rows, otherwise harmless filler. | ||
| if (line % 1000 == 0) text += query; | ||
| else text += "payload-"; | ||
| while (text.size() < 78) text += static_cast<char>('a' + (line % 26)); | ||
| text += "\r\n"; | ||
| batch += text; | ||
| } | ||
| parser.feed(batch); | ||
| const auto fill_end = std::chrono::steady_clock::now(); | ||
| Sleep(150); // Let the allocator settle before the snapshot is taken. | ||
| const Usage after_fill = current_usage(); | ||
|
|
||
| const TextDocument document(parser.screen); | ||
| TextSearch search; | ||
| const auto search_begin = std::chrono::steady_clock::now(); | ||
| const auto generation = search.start(document, query); | ||
| size_t steps = 0, work = 0; | ||
| while (search.status() == SearchStatus::running && steps < 1000000) { | ||
| const auto progress = search.step(document, generation, 4096); | ||
| work += progress.work_used; | ||
| ++steps; | ||
| } | ||
| const auto search_end = std::chrono::steady_clock::now(); | ||
|
|
||
| std::cout << "{\"history_limit\":" << history | ||
| << ",\"lines_fed\":" << lines | ||
| << ",\"retained\":" << document.size() | ||
| << ",\"history_rows\":" << parser.screen.history_size() | ||
| << ",\"query\":\"" << query << "\"" | ||
| << ",\"search_status\":" << static_cast<int>(search.status()) | ||
| << ",\"search_matches\":" << search.matches().size() | ||
| << ",\"search_steps\":" << steps | ||
| << ",\"search_work\":" << work | ||
| << ",\"fill_us\":" << micros(fill_begin, fill_end) | ||
| << ",\"search_us\":" << micros(search_begin, search_end) | ||
| << ",\"working_before\":" << before.working | ||
| << ",\"working_after\":" << after_fill.working | ||
| << ",\"private_before\":" << before.private_bytes | ||
| << ",\"private_after\":" << after_fill.private_bytes | ||
| << ",\"peak_working\":" << after_fill.peak_working | ||
| << "}" << std::endl; | ||
| return 0; | ||
| } |
32 changes: 32 additions & 0 deletions
32
experiments/core/results/2026-10-29-history-memory/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # 保留历史的内存与单次 Search 成本(#164 剩余项) | ||
|
|
||
| #164 的最后一项要求"在相同正确性下记录定位/重建次数、内存与首访/搜索成本"覆盖 100/1000/10000 行。定位/重建已由 [line-order 成本](https://github.com/JavaZeroo/termforge/blob/main/experiments/core/results/2026-10-29-line-order-r164/README.md) 提供;本片补**生产 `Screen` 的历史内存**与**生产 `TextSearch` 在 H=10000 的整文档扫描成本**。 | ||
|
|
||
| ## 方法 | ||
|
|
||
| 新增 `history_memory_probe`(`EXCLUDE_FROM_ALL`,默认不构建、不注册 CTest): | ||
|
|
||
| - 每个样本一个**新进程**,`Parser(24, 80, N)`,喂入 `N+40` 行(行宽 78 字节 + CRLF),每 1000 行含一次 `needle`; | ||
| - 用 `K32GetProcessMemoryInfo` 读工作集/私有提交/峰值,读点在填充前后各一次(中间 150ms 让分配器稳定); | ||
| - 用 `steady_clock` 记录填充与整文档 Search 的耗时、步数、work 单位与命中数; | ||
| - 每个 H 跑 3 个样本,原始 JSON 见 [validation.json](validation.json)。 | ||
|
|
||
| ## 结果(Windows 11 10.0.26200,Release,3 样本) | ||
|
|
||
| | H | 保留行 | 命中 | Search 步数 | Search work | 填充 µs | Search µs | 私有提交 前→后 | 工作集 前→后 | 每行私有增量 | | ||
| |---|---|---|---|---|---|---|---|---|---| | ||
| | 100 | 124 | 0(第 0 行已淘汰) | 3 | 9,965 | 590–752 | 867–1,200 | 0.69→2.54 MB | 4.35→5.17 MB | ~14.5 KB | | ||
| | 1000 | 1024 | 1 | 21 | 82,865 | 5,120–7,839 | 5,916–6,023 | 0.67→7.69 MB | 4.34→10.33 MB | ~6.9 KB | | ||
| | 10000 | 10024 | 10 | 199 | 811,865 | 53,969–65,463 | 53,389–58,331 | 0.68→58.97 MB | 4.35→61.15 MB | ~5.8 KB | | ||
|
|
||
| - 每行私有增量随 H 增大收敛到 **~5.8–6.9 KB**(H=100 含固定分配,故偏高);H=10000 时私有提交约 **59 MB**、工作集约 **61 MB**。 | ||
| - 整文档 Search 在 H=10000 为 **~54 ms**(199 步 × 4096 work),线性于行数;H=1000 为 ~6 ms。 | ||
| - 命中数符合预期:H=100 时第 0 行已淘汰、下一个命中在 1000 行且不在保留窗口内,故 0 命中;H=1000 保留 1 个;H=10000 保留 10 个。 | ||
|
|
||
| ## 解释与边界 | ||
|
|
||
| - 这是**生产** `Screen`/`TextSearch` 的观测,不是 r164 fork 的 Scan/LazyTable 对照;两者不可直接比较。 | ||
| - 每行成本主要来自 `Cell` 的 `std::string text` 与完整属性结构(80 列 × 约 72 B),本轮未尝试压缩或共享存储;该数值是后续历史压缩/大历史实验的基线输入,**不是性能预算**。 | ||
| - 峰值工作集等于最终工作集(没有额外瞬时峰值),但进程基线含运行时与二进制映像;每行数字取自前后差值。 | ||
| - 未覆盖:淘汰/重排混合、分块填充、真实 ConPTY 输出、UI 呈现、长期增长;Search 只测一条查询与一个命中分布。 | ||
| - 本片不关闭 #164:H=10000 的静态对照配方(r164 fork)与 RSS 之外的项目仍按原记录分别成立。 | ||
143 changes: 143 additions & 0 deletions
143
experiments/core/results/2026-10-29-history-memory/validation.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| { | ||
| "schema": "termforge-core-history-memory-v1", | ||
| "issue": 164, | ||
| "base_commit": "02c42e90114b535a9b5b88c566b504767fff8a61", | ||
| "configuration": { | ||
| "platform": "Windows-11-10.0.26200", | ||
| "generator": "Visual Studio 17 2022", | ||
| "build_type": "Release", | ||
| "binary": "history_memory_probe (EXCLUDE_FROM_ALL)" | ||
| }, | ||
| "method": { | ||
| "parser": "Parser(24, 80, N)", | ||
| "line_bytes": 80, | ||
| "hit_pattern": "one needle every 1000 rows", | ||
| "samples_per_size": 3, | ||
| "process": "fresh process per sample", | ||
| "memory_api": "K32GetProcessMemoryInfo" | ||
| }, | ||
| "samples": [ | ||
| { | ||
| "history": 100, | ||
| "retained": 124, | ||
| "matches": 0, | ||
| "steps": 3, | ||
| "work": 9965, | ||
| "fill_us": [ | ||
| 590, | ||
| 581, | ||
| 752 | ||
| ], | ||
| "search_us": [ | ||
| 987, | ||
| 867, | ||
| 1200 | ||
| ], | ||
| "private_before": [ | ||
| 741376, | ||
| 684032, | ||
| 688128 | ||
| ], | ||
| "private_after": [ | ||
| 2539520, | ||
| 2568192, | ||
| 2572288 | ||
| ], | ||
| "working_before": [ | ||
| 4837376, | ||
| 4349952, | ||
| 4354048 | ||
| ], | ||
| "working_after": [ | ||
| 5652480, | ||
| 5169152, | ||
| 5173248 | ||
| ] | ||
| }, | ||
| { | ||
| "history": 1000, | ||
| "retained": 1024, | ||
| "matches": 1, | ||
| "steps": 21, | ||
| "work": 82865, | ||
| "fill_us": [ | ||
| 5719, | ||
| 5120, | ||
| 7839 | ||
| ], | ||
| "search_us": [ | ||
| 6023, | ||
| 6006, | ||
| 5916 | ||
| ], | ||
| "private_before": [ | ||
| 671744, | ||
| 684032, | ||
| 684032 | ||
| ], | ||
| "private_after": [ | ||
| 7688192, | ||
| 7692288, | ||
| 7692288 | ||
| ], | ||
| "working_before": [ | ||
| 4337664, | ||
| 4345856, | ||
| 4349952 | ||
| ], | ||
| "working_after": [ | ||
| 10330112, | ||
| 10338304, | ||
| 10395648 | ||
| ] | ||
| }, | ||
| { | ||
| "history": 10000, | ||
| "retained": 10024, | ||
| "matches": 10, | ||
| "steps": 199, | ||
| "work": 811865, | ||
| "fill_us": [ | ||
| 65463, | ||
| 53969, | ||
| 55176 | ||
| ], | ||
| "search_us": [ | ||
| 58331, | ||
| 53796, | ||
| 53389 | ||
| ], | ||
| "private_before": [ | ||
| 675840, | ||
| 679936, | ||
| 679936 | ||
| ], | ||
| "private_after": [ | ||
| 58966016, | ||
| 58830848, | ||
| 58970112 | ||
| ], | ||
| "working_before": [ | ||
| 4341760, | ||
| 4345856, | ||
| 4345856 | ||
| ], | ||
| "working_after": [ | ||
| 61145088, | ||
| 61120512, | ||
| 61198336 | ||
| ] | ||
| } | ||
| ], | ||
| "source_sha256": { | ||
| "experiments/core/history_memory_probe.cpp": "787b4eaa7f8b13b27cc2429cf9f79412d60315fcb1ed3993388866b40e0ffba5", | ||
| "CMakeLists.txt": "ada254edee850f8570973ffa499ea64532507bf2a510e109e079a7231f45dc6b" | ||
| }, | ||
| "limitations": [ | ||
| "生产 Screen/TextSearch,不是 r164 fork 的 Scan/LazyTable 对照", | ||
| "未覆盖淘汰/重排混合、真实 ConPTY、UI 与长期增长", | ||
| "单查询单命中分布;不是性能预算或容差", | ||
| "进程基线含运行时与二进制映像" | ||
| ], | ||
| "performance_claim": false | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The statement that peak working set equals final working set cannot be checked against the published evidence:
validation.jsonomitspeak_workingfor every sample, although the probe emits it. Moreover, that peak is captured beforeTextSearchruns, so it cannot establish the absence of a transient peak across the whole experiment. Preserve the per-sample peak values and explicitly restrict this conclusion to the fill phase, or remove the unsupported conclusion.AGENTS.mdrequires raw samples for every performance claim.AGENTS.md reference: AGENTS.md:L12-L12
Useful? React with 👍 / 👎.