目标:验证 per-stream state object 是否真是热点;按 Receive → Send 两阶段最小化优化
当前 SendState / ReceiveState 是 class,每个新 flow-controlled stream 会创建 state object。但“存在 new”不等于“值得优化”。
固定流程:先隔离 state-object allocation → 排除 Dictionary resize / waiter → 先做简单的 ReceiveState 实验 → 再做需要 lifecycle generation 的 SendState 实验 → 真实流压测 → 不达标撤回。
当前核对基线:main = 201b1621cc706ee221e10ac20947af5fc62b087e。执行时必须记录实际 git rev-parse HEAD。
1. 已核对的当前实现
核心文件:
src/SharpLink.Runtime/StreamFlowController.cs
src/SharpLink.Runtime/RpcSession.cs
test/SharpLink.UnitTests/Runtime/StreamFlowControllerTests.cs
test/SharpLink.Benchmarks/RuntimeHotPathBenchmarks.cs
test/SharpLink.StreamLoadTest
当前 controller 持有:
Dictionary<StreamKey, SendState> _sendStates
Dictionary<StreamKey, ReceiveState> _receiveStates
LinkedList<CreditWaiter> _waiters
当前 state:
SendState class:
long Credit
bool Completed
Exception? AbortException
ReceiveState class:
long Credit
long PendingConsumed
bool Completed
SendState 生命周期
AcquireSendCreditAsync 第一次看到 key 时 AddSendState → new SendState(_streamWindow);
- state 可能在 stream completion 后作为 tombstone 留在 dictionary,直到最后 in-flight
WindowUpdate 把 credit 恢复到 _streamWindow;
_activeSendStreamCount 与 _sendStates.Count 有不同语义:completed tombstone 不算 active,但仍占 negotiated stream capacity;
- capacity 满且只是 tombstone pressure 时,最多保留一个等待 state-capacity 的
CreditWaiter。
ReceiveState 生命周期
AcceptReceived 第一次看到 key 时 new ReceiveState(_streamWindow);
RecordConsumed 累计 per-stream / connection consumed credit;
- stream threshold 或 connection threshold 时发 WindowUpdate;
FlushConsumed 把 state 标 completed,credit 全返后删除。
2. 一个必须先理解的关键 correctness 点:SendState 当前有“对象身份”语义
AcquireSendCreditAsync 在第一个 lock 内拿到 SendState? state,如果不能立即 reserve,会释放 lock 后调用:
AcquireContendedSendCreditAsync(key, expectedState, ...)
第二次拿 lock 时,如果同 key 已存在,会做:
expectedState != null && !ReferenceEquals(existingState, expectedState)
=> stream closed
这个 ReferenceEquals 不是多余的。
它防止以下 ABA-like lifecycle:
- 第一次 lookup 得到 stream lifecycle A 的 state;
- 释放 lock;
- A complete/remove;
- 同一个
(requestId, streamId) key 被新 lifecycle B 创建;
- 原调用重新拿 lock。
当前 class identity 能识别“key 相同,但已经不是同一个 state 生命周期”。
如果直接把 SendState 改成 struct 并删除这条 identity check,会改变并发语义。禁止这样做。
3. 第一性原理拆分假设
必须分别验证:
- H1:短生命周期 receive stream 的
ReceiveState object allocation 是可观 B/stream 成本。
- H2:短生命周期 send stream 的
SendState object allocation 是可观 B/stream 成本。
- H3:把 state inline 到 Dictionary entry 后,节省的 heap allocation 大于更大的 Dictionary entry / value-copy / ref-mutation 成本。
- H4:优化不能把无 contention fast path 变成
CollectionsMarshal 误用、额外 hash lookup 或锁内复杂度热点。
Receive 和 Send 必须分别做 candidate。不要一次性把两个 class 都改掉,然后无法归因回退。
4. Phase 0:baseline correctness
先执行:
dotnet build Sharplink.slnx -c Release -v minimal
dotnet test --project test/SharpLink.UnitTests/SharpLink.UnitTests.csproj -c Release
dotnet run -c Release --project test/SharpLink.IntegrationTests
必须保持 StreamFlowControllerTests 当前已有语义:
- 1-byte window block/restore;
- oversized item borrow once / exact repay;
- waiter cancellation / connection close;
- FIFO connection credit;
- stream-credit blocked head 不阻塞其他 eligible stream;
- receive half-window batching;
- connection threshold flush 多 stream pending credit;
- failed/completed send tombstone 接受 late WindowUpdate;
- tombstone capacity backpressure;
- pending replacement waiter bound;
- ReturnUnsentCredit;
- unknown WindowUpdate → ProtocolViolation;
- AbortSendStreams;
- rejected StreamComplete 释放 slot。
5. Phase 1:先证明 allocation 真来自 state object
扩展 FlowControlHotPathBenchmarks 或新增独立 benchmark class。
5.1 Send no-wait workload
固定场景:
create/use stream key
AcquireSendCreditAsync
ApplyWindowUpdate exact credit
CompleteSendStream
确保 credit 足够、_waiters.Count == 0,不要触发 CreditWaiter。
参数:
- items per stream:
1 / 4 / 64
- active streams:
1 / 8 / 32 / 128
- encoded bytes:固定小值,例如 32
5.2 Receive workload
AcceptReceived
RecordConsumed / threshold flush
FlushConsumed
同样覆盖 1 / 4 / 64 items 和 1 / 8 / 32 / 128 streams。
5.3 长生命周期控制组
一个 stream 处理大量 items,state 只创建一次。
如果 long-lived stream B/item 已接近 0,而 short stream B/stream 很高,才支持本 issue 的假设。
5.4 排除 Dictionary resize
第一次字典扩容会分配 entry/bucket arrays,不能把它算成 state object。
正式测量前:
- 用足够多 stream warm-up controller/dictionary capacity;
- 然后完成/remove states;
- 再测新的 key;
- profiler allocation stack 必须区分
new SendState/ReceiveState 与 Dictionary.Resize。
不要通过直接给 production dictionary EnsureCapacity(maxConcurrentStreams) 来“清理 benchmark”,那本身是另一个 memory tradeoff 实验。
5.5 排除 waiter path
baseline state-allocation benchmark 不允许 exhausted credit,因为 CreditWaiter 当前会创建:
CreditWaiter
TaskCompletionSource<bool>
LinkedListNode
- cancellation registration / async Task path
这些不属于本 issue 第一阶段。
6. Phase 1 停止条件
如果 allocation stack 显示主要成本是:
- Dictionary resize;
CreditWaiter;
- caller async state machine;
- stream dispatcher;
而 SendState/ReceiveState 只占很小部分,则停止本 issue,不要为了两个 object 强改状态机。
7. Phase 2A:优先只转换 ReceiveState
ReceiveState 风险更低,因为它没有跨 lock 保存 object identity。
候选:
private struct ReceiveState
{
public long Credit;
public long PendingConsumed;
public bool Completed;
}
7.1 最大陷阱:Dictionary.TryGetValue 返回 struct copy
当前 class 代码:
_tryGet -> state
state.Credit -= bytes
改成 struct 后如果保持同样写法,修改只发生在 local copy,dictionary 内状态不会更新。
禁止“只把 class 改 struct,编译过就算完成”。
7.2 推荐 mutation 规则
在 _gate 已经持有的情况下,可以使用 BCL CollectionsMarshal 获取 Dictionary value ref,但必须严格遵守 ref lifetime:
ref 只在持有 _gate 时使用;
- ref 不得逃逸方法;
- ref 存活期间 不能对同一个 dictionary 做 Add/Remove/Clear/可能 resize 的操作;
- 需要 remove 时,先从 ref 读出
shouldRemove,结束 ref 使用,再 _receiveStates.Remove(key)。
概念例:
lock gate:
if key missing:
capacity check
add default/new state
ref state = ref GetValueRef...
mutate credit/pending
shouldRemove = ...
// stop using ref
if shouldRemove: Remove(key)
7.3 TakePendingCredit
当前 helper 接收 class reference。改 struct 后必须是:
TakePendingCredit(ref ReceiveState state)
否则 pending count 清零只改 local copy。
7.4 FlushPendingConnectionCredit
当前方法 foreach _receiveStates 并直接 mutate pair.Value class。
struct 后不能 mutate foreach copy。推荐:
- 遍历
_receiveStates.Keys;
- 对当前 key 使用
CollectionsMarshal.GetValueRefOrNullRef;
- 只 mutate value,不在 ref 存活时改变 dictionary structure;
- completed keys 继续收集到当前已有的
List<StreamKey>? completed;
- iteration 完成后统一 Remove。
不要在 foreach 里 _receiveStates[key] = state 并假设 Dictionary enumeration version 一定允许;不要依赖未明确约定的实现细节。
7.5 Phase 2A gate
Receive-only candidate 单独测:
- B/receive-stream;
- ns/item;
- connection-threshold flush;
- 128 active streams。
如果 allocation 降低很小或 CPU/entry footprint 回退明显,撤回 Receive struct,Send 不再继续。
8. Phase 2B:SendState struct 必须增加 lifecycle generation
只有 H2 成立且 Receive experiment 证明 inline state 值得做,才继续。
候选概念:
private struct SendState
{
public long Credit;
public long Generation;
public bool Completed;
public Exception? AbortException;
}
private long _nextSendStateGeneration;
generation 在 _gate 下创建新 SendState 时递增;只要求同一个 controller 生命周期内不会正常重复。
8.1 替代 ReferenceEquals
第一次 AcquireSendCreditAsync lookup/create 后,不再把 SendState? expectedState 传到 slow path,而是传:
约定:
0 表示第一次 lock 没有绑定到已有/新 state(例如 tombstone capacity 满);
- 非 0 表示调用已经观察到某个确切 lifecycle。
第二次 lock:
if key exists:
if expectedGeneration != 0 && state.Generation != expectedGeneration:
throw stream-closed
else:
if expectedGeneration != 0:
throw stream-closed
if capacity available:
create new state with new generation
这才等价于当前 class identity guard。
必须新增 deterministic test:在第一次 lookup 和 slow-path reacquire 之间 remove old state + recreate same key,旧 operation 必须拒绝,不能操作新 lifecycle。
8.2 所有 SendState mutation 都必须 ref-safe
至少核对这些方法:
AcquireSendCreditAsync
AcquireContendedSendCreditAsync
ApplyWindowUpdate
ReturnUnsentCredit
CompleteSendStream
AbortSendStreams
AddSendState(可能需要改成 add/ref helper)
Reserve
AdmitWaiters
Reserve 必须变成 ref SendState 语义;否则 credit 扣减会丢失。
8.3 Add/Remove 与 ref lifetime
例如 ApplyWindowUpdate:
ref state = dictionary value
mutate
shouldRemove = state.Completed && state.Credit == streamWindow
stop using ref
if shouldRemove: Remove(key)
ready = AdmitWaiters()
不要持有 dictionary value ref 再调用 AdmitWaiters(),因为后者可能 Add 新 state 并触发 resize。
8.4 AbortSendStreams
当前 foreach pair.Value class 可直接 mutate;struct 后必须重写。
推荐:
- 遍历 keys;
- 对 matching requestId 获取 ref 并 mutate;
- completed remove keys 收集到 list;
- iteration 后 Remove;
- 再处理 waiters;
- 再
AdmitWaiters()。
不能边持 ref 边 remove。
8.5 Exception reference cleanup
SendState 即使是 struct,Dictionary entry 仍可能持有 AbortException reference。
必须保持:
- state remove 后 exception 可释放;
- controller
Complete 时 _sendStates.Clear();
- aborted tombstone 的保留时长与当前一致;
- 不为了减少 allocation 把 exception message/code 丢掉。
9. CollectionsMarshal 是候选实现工具,不是优化目标
不要因为 issue 文本提到它就强制使用。
如果简单 copy-update-writeback 能正确覆盖某个不在 enumeration 中的路径,可以先 benchmark;如果 extra hash lookup 可忽略,简单代码优先。
但以下路径需要特别小心:
- foreach mutation;
Reserve 多次修改;
- Add 后立即拿 state;
- tombstone remove。
只有在 correctness 清晰时使用 CollectionsMarshal。
如果 ref-safety 让代码变得难以审计,宁可撤回 struct 方案。性能优化不能降低状态机可验证性。
10. 备选方案只有在 struct 失败后再试
state object reuse/pool
不作为第一候选,因为它会引入:
- state pool 自身同步;
AbortException reference cleanup;
- send tombstone 生命周期;
- ABA/reuse identity;
- bounded retention。
如果 struct 因 Dictionary value size / copy cost 明显回退,且 state allocation 又确实是大热点,才单独设计 pool experiment。
不要在同一个 PR 同时 struct + pool + waiter optimization。
11. 必须新增的 correctness tests
ReceiveState
- first AcceptReceived 创建 state;
- exact stream credit exhaustion;
- connection credit exhaustion;
- RecordConsumed 小于 threshold;
- 达 stream threshold flush;
- 达 connection threshold 时 flush 多 stream;
- FlushConsumed 后 pending credit exactly once;
- completed + partial outstanding credit 保留 state;
- credit 全返后 remove;
- duplicate consumed/overflow → ProtocolViolation;
- 128 receive states 容量边界。
SendState
- first Acquire 创建 state + generation;
- fast reserve;
- credit exhaustion → waiter;
- WindowUpdate restore;
- duplicate/overflow WindowUpdate;
- CompleteSendStream with full credit → immediate remove;
- Complete with outstanding credit → tombstone;
- late WindowUpdate tombstone → remove;
- ReturnUnsentCredit exactly once;
- AbortSendStreams reclaim connection credit;
- abort + completion idempotence;
- max concurrent active limit;
- tombstone pressure + one replacement waiter;
- canceled replacement waiter slot reuse;
- shared connection credit across multiple streams;
- same key lifecycle ABA test:old expected generation 不能命中新 state;
- generation 不因 remove/re-add 被复用;
Complete(exception) 清空 states/waiters。
并发
保留 stress,并增加 barrier 协调:
- Acquire slow path 在 lock gap 中 Complete/Remove/Recreate same key;
- ApplyWindowUpdate 与 CompleteSendStream;
- AbortSendStreams 与 pending waiter;
- receive RecordConsumed / FlushConsumed 顺序边界。
12. 性能门禁
baseline/candidate 同机交替至少 5 轮。
目标指标
- B/stream
- B/item
- ns/stream
- ns/item
- throughput
- CPU
- lock contention
- GC
- Dictionary retained memory(至少观察 process heap/working set,避免 heap object 少了但 entry 变大很多)。
预先固定判定
- short stream B/stream 必须看到能归因到 state object 的明显下降;
- long-lived stream 不应有实质回退,因为它原本只分配一次 state;
- 1 active stream fast path不应稳定回退 >2%;
- 32/128 active stream throughput/lock contention 不应稳定恶化 >5%;
- 如果 state allocation 降低,但 Dictionary 更大的 inline value 导致 CPU/cache 明显变差,撤回;
- waiter-heavy workload 不要求本 issue 变快,但不能因 state refactor 明显变慢。
13. 真实 StreamLoadTest
至少:
c2s
s2c
duplex
- 短 stream:1 / 4 / 16 items
- 中等:64 / 256
- 长 stream:1024+
- concurrency:
1 / 8 / 32 / 128
- TCP + SharedMemory;平台允许时 UDS / NamedPipe。
短流是主要目标;长流是 regression control。
记录 P50/P95/P99、streams/s、items/s、allocation、GC、CPU、failures。
14. commit / rollback 顺序
推荐严格拆分:
bench: isolate stream flow state allocations
perf: inline receive flow state
test: cover receive struct mutation paths
perf: add send state lifecycle generation
perf: inline send flow state
test: cover send lifecycle ABA and tombstones
不要把 Receive + Send 一次提交。
任何阶段:
- allocation 没按预期下降;
- deterministic test 失败;
- same-key lifecycle guard 无法等价;
- contention/cache 回退超门槛;
就 git revert 对应 candidate。
不允许通过删 tombstone test、放宽 unknown WindowUpdate、取消 generation check 来救性能数字。
15. 最终验收
只有数据证明有价值的部分需要合并。最终必须满足:
- allocation stack 已证明 state object 是热点;
- Dictionary resize / waiter 已排除;
- ReceiveState mutation 全部持久化,无 struct-copy bug;
- SendState 保留当前 object identity 等价语义,通过 generation 防 same-key lifecycle ABA;
- stream/connection 双重 credit 不变;
- completed tombstone 不变;
- late WindowUpdate 不变;
- AbortSendStreams 不变;
- ReturnUnsentCredit 不变;
- max stream/tombstone pressure 不变;
- receive threshold / connection threshold flush 不变;
- ProtocolViolation/ResourceExhausted/ConnectionClosed 分类不变;
- Release unit/integration + NativeAOT smoke 通过;
- 短流真实 workload 有稳定收益;
- 长流和高并发无实质回退。
如果最终只是“少了两个 object”,但总 CPU/latency/内存没有变好:撤回,保留 class state。
目标:验证 per-stream state object 是否真是热点;按 Receive → Send 两阶段最小化优化
当前核对基线:
main=201b1621cc706ee221e10ac20947af5fc62b087e。执行时必须记录实际git rev-parse HEAD。1. 已核对的当前实现
核心文件:
src/SharpLink.Runtime/StreamFlowController.cssrc/SharpLink.Runtime/RpcSession.cstest/SharpLink.UnitTests/Runtime/StreamFlowControllerTests.cstest/SharpLink.Benchmarks/RuntimeHotPathBenchmarks.cstest/SharpLink.StreamLoadTest当前 controller 持有:
当前 state:
SendState 生命周期
AcquireSendCreditAsync第一次看到 key 时AddSendState→new SendState(_streamWindow);WindowUpdate把 credit 恢复到_streamWindow;_activeSendStreamCount与_sendStates.Count有不同语义:completed tombstone 不算 active,但仍占 negotiated stream capacity;CreditWaiter。ReceiveState 生命周期
AcceptReceived第一次看到 key 时new ReceiveState(_streamWindow);RecordConsumed累计 per-stream / connection consumed credit;FlushConsumed把 state 标 completed,credit 全返后删除。2. 一个必须先理解的关键 correctness 点:SendState 当前有“对象身份”语义
AcquireSendCreditAsync在第一个 lock 内拿到SendState? state,如果不能立即 reserve,会释放 lock 后调用:第二次拿 lock 时,如果同 key 已存在,会做:
这个
ReferenceEquals不是多余的。它防止以下 ABA-like lifecycle:
(requestId, streamId)key 被新 lifecycle B 创建;当前 class identity 能识别“key 相同,但已经不是同一个 state 生命周期”。
如果直接把
SendState改成 struct 并删除这条 identity check,会改变并发语义。禁止这样做。3. 第一性原理拆分假设
必须分别验证:
ReceiveStateobject allocation 是可观 B/stream 成本。SendStateobject allocation 是可观 B/stream 成本。CollectionsMarshal误用、额外 hash lookup 或锁内复杂度热点。Receive 和 Send 必须分别做 candidate。不要一次性把两个 class 都改掉,然后无法归因回退。
4. Phase 0:baseline correctness
先执行:
dotnet build Sharplink.slnx -c Release -v minimal dotnet test --project test/SharpLink.UnitTests/SharpLink.UnitTests.csproj -c Release dotnet run -c Release --project test/SharpLink.IntegrationTests必须保持
StreamFlowControllerTests当前已有语义:5. Phase 1:先证明 allocation 真来自 state object
扩展
FlowControlHotPathBenchmarks或新增独立 benchmark class。5.1 Send no-wait workload
固定场景:
确保 credit 足够、
_waiters.Count == 0,不要触发CreditWaiter。参数:
1 / 4 / 641 / 8 / 32 / 1285.2 Receive workload
同样覆盖
1 / 4 / 64 items和1 / 8 / 32 / 128 streams。5.3 长生命周期控制组
一个 stream 处理大量 items,state 只创建一次。
如果 long-lived stream B/item 已接近 0,而 short stream B/stream 很高,才支持本 issue 的假设。
5.4 排除 Dictionary resize
第一次字典扩容会分配 entry/bucket arrays,不能把它算成 state object。
正式测量前:
new SendState/ReceiveState与Dictionary.Resize。不要通过直接给 production dictionary
EnsureCapacity(maxConcurrentStreams)来“清理 benchmark”,那本身是另一个 memory tradeoff 实验。5.5 排除 waiter path
baseline state-allocation benchmark 不允许 exhausted credit,因为
CreditWaiter当前会创建:CreditWaiterTaskCompletionSource<bool>LinkedListNode这些不属于本 issue 第一阶段。
6. Phase 1 停止条件
如果 allocation stack 显示主要成本是:
CreditWaiter;而
SendState/ReceiveState只占很小部分,则停止本 issue,不要为了两个 object 强改状态机。7. Phase 2A:优先只转换
ReceiveStateReceiveState 风险更低,因为它没有跨 lock 保存 object identity。
候选:
7.1 最大陷阱:
Dictionary.TryGetValue返回 struct copy当前 class 代码:
改成 struct 后如果保持同样写法,修改只发生在 local copy,dictionary 内状态不会更新。
禁止“只把 class 改 struct,编译过就算完成”。
7.2 推荐 mutation 规则
在
_gate已经持有的情况下,可以使用 BCLCollectionsMarshal获取 Dictionary value ref,但必须严格遵守 ref lifetime:ref只在持有_gate时使用;shouldRemove,结束 ref 使用,再_receiveStates.Remove(key)。概念例:
7.3
TakePendingCredit当前 helper 接收 class reference。改 struct 后必须是:
否则 pending count 清零只改 local copy。
7.4
FlushPendingConnectionCredit当前方法 foreach
_receiveStates并直接 mutatepair.Valueclass。struct 后不能 mutate foreach copy。推荐:
_receiveStates.Keys;CollectionsMarshal.GetValueRefOrNullRef;List<StreamKey>? completed;不要在 foreach 里
_receiveStates[key] = state并假设 Dictionary enumeration version 一定允许;不要依赖未明确约定的实现细节。7.5 Phase 2A gate
Receive-only candidate 单独测:
如果 allocation 降低很小或 CPU/entry footprint 回退明显,撤回 Receive struct,Send 不再继续。
8. Phase 2B:
SendStatestruct 必须增加 lifecycle generation只有 H2 成立且 Receive experiment 证明 inline state 值得做,才继续。
候选概念:
generation 在
_gate下创建新 SendState 时递增;只要求同一个 controller 生命周期内不会正常重复。8.1 替代
ReferenceEquals第一次
AcquireSendCreditAsynclookup/create 后,不再把SendState? expectedState传到 slow path,而是传:约定:
0表示第一次 lock 没有绑定到已有/新 state(例如 tombstone capacity 满);第二次 lock:
这才等价于当前 class identity guard。
必须新增 deterministic test:在第一次 lookup 和 slow-path reacquire 之间 remove old state + recreate same key,旧 operation 必须拒绝,不能操作新 lifecycle。
8.2 所有 SendState mutation 都必须 ref-safe
至少核对这些方法:
AcquireSendCreditAsyncAcquireContendedSendCreditAsyncApplyWindowUpdateReturnUnsentCreditCompleteSendStreamAbortSendStreamsAddSendState(可能需要改成 add/ref helper)ReserveAdmitWaitersReserve必须变成ref SendState语义;否则 credit 扣减会丢失。8.3 Add/Remove 与 ref lifetime
例如
ApplyWindowUpdate:不要持有 dictionary value ref 再调用
AdmitWaiters(),因为后者可能 Add 新 state 并触发 resize。8.4
AbortSendStreams当前 foreach
pair.Valueclass 可直接 mutate;struct 后必须重写。推荐:
AdmitWaiters()。不能边持 ref 边 remove。
8.5 Exception reference cleanup
SendState即使是 struct,Dictionary entry 仍可能持有AbortExceptionreference。必须保持:
Complete时_sendStates.Clear();9.
CollectionsMarshal是候选实现工具,不是优化目标不要因为 issue 文本提到它就强制使用。
如果简单 copy-update-writeback 能正确覆盖某个不在 enumeration 中的路径,可以先 benchmark;如果 extra hash lookup 可忽略,简单代码优先。
但以下路径需要特别小心:
Reserve多次修改;只有在 correctness 清晰时使用
CollectionsMarshal。如果 ref-safety 让代码变得难以审计,宁可撤回 struct 方案。性能优化不能降低状态机可验证性。
10. 备选方案只有在 struct 失败后再试
state object reuse/pool
不作为第一候选,因为它会引入:
AbortExceptionreference cleanup;如果 struct 因 Dictionary value size / copy cost 明显回退,且 state allocation 又确实是大热点,才单独设计 pool experiment。
不要在同一个 PR 同时 struct + pool + waiter optimization。
11. 必须新增的 correctness tests
ReceiveState
SendState
Complete(exception)清空 states/waiters。并发
保留 stress,并增加 barrier 协调:
12. 性能门禁
baseline/candidate 同机交替至少 5 轮。
目标指标
预先固定判定
13. 真实 StreamLoadTest
至少:
c2ss2cduplex1 / 8 / 32 / 128短流是主要目标;长流是 regression control。
记录 P50/P95/P99、streams/s、items/s、allocation、GC、CPU、failures。
14. commit / rollback 顺序
推荐严格拆分:
bench: isolate stream flow state allocationsperf: inline receive flow statetest: cover receive struct mutation pathsperf: add send state lifecycle generationperf: inline send flow statetest: cover send lifecycle ABA and tombstones不要把 Receive + Send 一次提交。
任何阶段:
就
git revert对应 candidate。不允许通过删 tombstone test、放宽 unknown WindowUpdate、取消 generation check 来救性能数字。
15. 最终验收
只有数据证明有价值的部分需要合并。最终必须满足:
如果最终只是“少了两个 object”,但总 CPU/latency/内存没有变好:撤回,保留 class state。