l4 evict and swiss api improve

This commit is contained in:
Youmu Liu
2026-10-20 18:47:31 +08:00
parent e349bcc4dd
commit 3951dd6686
7 changed files with 140 additions and 40 deletions
@@ -16,6 +16,10 @@ namespace Nitload.Common.HighPerformance.Memory
public unsafe partial class L4
{
private int _active_line_count;
private int _last_evict_pos;
internal partial int Predict()
{
if (!TryGetCurrentPredictor(out IL4Predictor? p)) {
@@ -23,12 +27,13 @@ namespace Nitload.Common.HighPerformance.Memory
}
int total_count = 0, count;
Span<L4.AddressSource> as_sp = stackalloc L4.AddressSource[4];
SwissEnumerator e = _addressMap.GetWriteBufferEnumerator();
Predict:
while ((count = p.PredictMission(in as_sp)) > 0)
{
for (int i = 0; i < count; i++)
{
if (_addressMap.TryGet(as_sp[i], out ulong map_handle_adr))
if (_addressMap.TryGetWrite(as_sp[i], out ulong map_handle_adr))
{
L4.CacheLineMetadata* map_handle = (L4.CacheLineMetadata*)map_handle_adr;
map_handle->IncreasePredictCount();
@@ -38,13 +43,31 @@ namespace Nitload.Common.HighPerformance.Memory
uint size = as_sp[i].Size;
int handle_idx = _bufferAllocator.Alloc(size);
L4.CacheLineMetadata* handle = _bufferAllocator.MetadataAt(handle_idx);
p.Prefetch(as_sp[i], handle->Position, handle->SizeMask);
if (!_addressMap.TryAddOrSet(as_sp[i], (ulong)handle)) {
throw new InvalidOperationException();
}
_active_line_count++;
if (_active_line_count >= MaxActiveCacheLineCount)
{
int retry = 0;
const int max_retry = 3;
for (; retry < max_retry && EvictOnce(ref e); retry++) {
e = _addressMap.GetWriteBufferEnumerator(_last_evict_pos);
}
if (retry >= max_retry) {
throw new OverflowException("Evict failed, maybe all cache lines are in use.");
}
_active_line_count--;
}
}
}
total_count += count;
if (total_count >= 2048) {
if (total_count >= 2048)
{
_ = Interlocked.Add(ref _predict_count, total_count);
_last_evict_pos = e.CurrentOffset;
return total_count;
}
}
@@ -53,22 +76,36 @@ namespace Nitload.Common.HighPerformance.Memory
_ = TryGetCurrentPredictor(out p);
goto Predict;
}
_ = Interlocked.Add(ref _predict_count, total_count);
_last_evict_pos = e.CurrentOffset;
return total_count;
}
private bool EvictOnce(
ref SwissEnumerator e
)
{
while (e.MoveNext())
{
(AddressSource source, ulong cache_head_ptr) = e.Current;
L4.CacheLineMetadata* handle = (L4.CacheLineMetadata*)cache_head_ptr;
if (Volatile.Read(ref handle->ConsumeCount) == Volatile.Read(ref handle->ConsumeCount))
{
// evict
_ = _addressMap.TryRemove(source, out _);
_bufferAllocator.Free(handle);
return true;
}
}
return false;
}
}
}
/*
goto 本身没问题,但当前控制流有几个需要先确认的点:
高:换 predictor 的分支看起来反了。 MoveNextPredictor() 在 _predict 到达 _enter 时返回 true。因此 L4.Predict.cs:51 现在会在队列耗尽时再次取 predictor;TryGetCurrentPredictor 失败后仍 goto Predict,接着会用空的 p 调 PredictMission。反过来,有下一个 predictor 时它会提前返回,不再处理后续 predictor。
高:新分配的 metadata 没有增加预测计数。 现有映射分支会调用 IncreasePredictCount(),但新分配分支只分配并写入 Swiss。若该计数表示未消费预测数,新条目也需要相同的计数处理;还要确认 TLSF 重用 metadata 时会重置旧计数。
高:同一轮内重复的 AddressSource 可能重复分配。 SwissTable.TryGet 只查 published buffer,而新条目先写入 inactive buffer。后续再次预测相同 key 时会查不到,重新分配 metadata,再由 TryAddOrSet 覆盖先前 value,旧分配就失去映射。
中:需要校验 PredictMission 的返回值范围。 Span 容量是 4;count > 4 会让 as_sp[i] 越界,负值则被当作本轮结束。建议契约明确要求 0..4,并在 L4 边界验证。
本轮未改代码。L4.Predict.cs 的文件诊断通过,但尚未运行完整构建或运行时测试。
*/
// Eviction follow-up:
// 1. Compare ConsumeCount with PredictCount; reset both on TLSF slot reuse and count new predictions.
// 2. Fix retry/result handling; decrement _active_line_count only after a successful eviction.
// 3. Correct TLSF reverse-slab pointer validation before freeing metadata pointers.
// 4. Defer TLSF Free until retired published buffers and outstanding handles can no longer reference the line.
// 5. Add Swiss TryRemoveAt(offset) to tombstone the enumerated slot without hashing and probing again.