Add XCode skills for entity caching, ORM, and sharding ETL
|
---
name: high-performance-buffers
description: >
è®¾è®¡é›¶æ‹·è´æˆ–å°‘æ‹·è´çš„二进制缓冲区抽象,覆盖切片共享底层内å˜ã€é“¾å¼æ‹¼æŽ¥ã€æ‰€æœ‰æƒï¼ˆOwner)转移与释放ã€Span/Memory çš„çŸç”Ÿå‘½å‘¨æœŸçº¦æŸï¼Œ
ä»¥åŠ ArrayPool 的租借/归还模å¼ã€‚适用于网络收å‘ã€å议解æžã€äºŒè¿›åˆ¶ç»„包ç‰é«˜é¢‘场景的设计与代ç 审查。
argument-hint: >
æè¿°ä½ 的缓冲区场景:Socket 接收/å‘é€ã€å议解æžåˆ‡ç‰‡ã€è·¨å±‚ä¼ é€’æ•°æ®æ®µï¼›
说明是å¦éœ€è¦æ± åŒ–ã€æ˜¯å¦éœ€è¦æ‰€æœ‰æƒè½¬ç§»ã€å¤šæ®µæ‹¼æŽ¥è¿˜æ˜¯å•段读å–。
---
# 高性能缓冲区设计技能
## 适用场景
- 需è¦å¯¹æŽ¥æ”¶åˆ°çš„网络å—节æµåšé›¶æ‹·è´å议头/å议体切片,并将ä¸åŒæ®µä¼ 递给ä¸åŒå±‚处ç†ã€‚
- 组装多段数æ®ï¼ˆå¤´éƒ¨ + è´Ÿè½½ + 尾部)并å‘é€ï¼Œå¸Œæœ›é¿å…èšåˆå¤åˆ¶ã€‚
- 使用 `ArrayPool<T>` 租借缓冲区用于接收并在完æˆåŽå½’è¿˜ï¼Œéœ€è¦æ˜Žç¡®é‡Šæ”¾è´£ä»»å½’属。
- 多个消费者需è¦å¯¹åŒä¸€æ®µæ•°æ®åšåªè¯»è®¿é—®ï¼Œæˆ–者æŸä¸€æ®µå¿…é¡»ç¦æ¢ä¿®æ”¹ã€‚
- å·²æœ‰è¿žç»æ•°ç»„缓冲区,需è¦åŒ…装为统一抽象对外暴露,ä¸äº§ç”Ÿé¢å¤–å¤åˆ¶ã€‚
## æ ¸å¿ƒåŽŸåˆ™
1. **å‡å°‘分é…**:优先å¤ç”¨ç¼“冲区(`ArrayPool<T>.Shared`ï¼‰ï¼›åˆ‡ç‰‡æ—¶å…±äº«åº•å±‚æ•°ç»„ï¼Œè€Œä¸æ˜¯å¤åˆ¶åˆ°æ–°æ•°ç»„。
2. **å‡å°‘æ‹·è´**:`Slice(offset, count)` 始终共享底层内å˜ï¼Œä»…记录åç§»å’Œé•¿åº¦ï¼›åªæœ‰åœ¨ç¡®å®žéœ€è¦ç‹¬ç«‹å‰¯æœ¬æ—¶æ‰è°ƒç”¨ `ToArray()` 或 `Clone()`。
3. **链å¼è€Œéžèšåˆ**:通过 `Next` 串接多段数æ®ï¼ˆ`Append`),é¿å…为大包预分é…一å—连ç»å†…å˜å†å¤åˆ¶ï¼›åœ¨éœ€è¦è¿žç»å†…å˜æ—¶æŒ‰éœ€è°ƒç”¨ `ToArray()`。
4. **明确所有æƒ**ï¼šæ± åŒ–ç¼“å†²åŒºå¿…é¡»æœ‰ä¸”åªæœ‰ä¸€æ–¹è´Ÿè´£é‡Šæ”¾ï¼›æ‰€æœ‰æƒé€šè¿‡ `IOwnerPacket.Dispose()` è¯ä¹‰è¡¨è¾¾ï¼Œéžæ± 化缓冲区ä¸éœ€è¦ä¹Ÿä¸åº”å¼ºåŠ é‡Šæ”¾è´£ä»»ã€‚
5. **Span/Memory çŸç”Ÿå‘½å‘¨æœŸ**:`GetSpan()` / `GetMemory()` æ˜¯å€Ÿç”¨è§†å›¾ï¼›ç¦æ¢å°†å…¶ä¿å˜åˆ°å—段ã€é—包ã€é˜Ÿåˆ—ã€å¼‚æ¥å›žè°ƒä¸â€”â€”ä»…åœ¨è°ƒç”¨æ ˆå†…åŒæ¥ä½¿ç”¨ã€‚
## 执行æ¥éª¤
### 一ã€é€‰æ‹©åŒ…类型
| 场景 | 推è类型 |
|------|----------|
| é«˜é¢‘ã€æ— æ± åŒ–ã€ä¼ 递方å¼å¤šæ · | `ArrayPacket`(值类型,轻é‡ï¼‰|
| 从 `ArrayPool` 租借缓冲区 | `OwnerPacket`(需 `Dispose`)|
| 已有 `Memory<Byte>` å¤–éƒ¨æ¥æº | `MemoryPacket`ï¼ˆæ— æ‰€æœ‰æƒï¼‰|
| 多线程共享åªè¯»å¸¸é‡æ•°æ® | `ReadOnlyPacket` |
> åŒä¸€é“¾ä¸æ··åˆç±»åž‹éœ€è°¨æ…Žï¼š`ArrayPacket` 跨段切片å‡è®¾ `Next` 也是 `ArrayPacket`,混链å¯èƒ½å¯¼è‡´è½¬åž‹å¼‚常。
### 二ã€å®žçŽ°åˆ‡ç‰‡è¯ä¹‰
1. **共享切片(默认)**:`Slice(offset, count)` — 返回共享底层缓冲区的新视图,ä¸åˆ†é…。
2. **视图借用**:`Slice(offset, count, transferOwner: false)` — 明确表达"仅借视图ã€ä¸è½¬ç§»è´£ä»»"。
3. **所有æƒè½¬ç§»**:`Slice(offset, count, transferOwner: true)` — 新包æˆä¸ºæ‰€æœ‰è€…,原包失去释放æƒï¼›**åªèƒ½å‘生一次**,且必须明确记录哪一方最终负责 `Dispose`。
> `OwnerPacket.Slice(offset, count)` çš„**默认行为是转移所有æƒ**,与一般共享切片è¯ä¹‰ç›¸å。在多次切片时应显å¼ä¼ å…¥ `transferOwner: false`。
### 三ã€é“¾å¼æ‹¼æŽ¥
1. 使用 `Append(next)` åœ¨é“¾å°¾è¿½åŠ ï¼Œæ—¶é—´å¤æ‚度 O(n)ï¼›é“¾æ¡æžé•¿æ—¶æ”¹ä¸ºæž„å»ºé“¾èŠ‚ç‚¹æ ‘æˆ–é¢„å…ˆè§„åˆ’ç»“æž„ã€‚
2. `Length` ä»…åæ˜ 当剿®µï¼›`Total` åæ˜ æ•´é“¾æ€»é•¿â€”â€”åˆ¤æ–æ˜¯å¦ä¸ºç©ºåº”看 `Total`。
3. `ToArray()` 总是å¤åˆ¶ï¼›`ToSegments()` ä¿æŒåˆ†æ®µç»“æž„ï¼ˆé€‚åˆ Scatter/Gather IO)。
4. 头部扩展使用 `ExpandHeader(size)`:有å‰ç½®ç©ºé—´æ—¶åŽŸåœ°æ‰©å±•ï¼Œå¦åˆ™ç”Ÿæˆæ–°å¤´èŠ‚ç‚¹å¹¶æŒ‚è½½åŽŸåŒ…ä¸º `Next`。
### å››ã€æ‰€æœ‰æƒç®¡ç†
1. æŒæœ‰ `IOwnerPacket` 的一方负责最终释放;`Dispose()` 通常会递归释放 `Next` 链。
2. åœ¨æµæ°´çº¿åœºæ™¯ï¼ˆå¦‚:接收 → è§£æž â†’ 应用层触å‘),最好在最外层用 `using` 声明所有æƒï¼Œå†…层函数通过 `transferOwner: false` ä¼ é€’è§†å›¾ï¼›ä»…åœ¨æœ€ç»ˆè¿”å›ž/ä¿å˜çš„结果上转移。
3. 调用 `Free()` 会清空引用但**ä¸**å½’è¿˜æ± åŒ–å†…å˜ï¼Œå˜åœ¨æ³„æ¼é£Žé™©ï¼›ä»…在确实需è¦"放弃但ä¸é‡Šæ”¾"的特殊场景ä¸ä½¿ç”¨ã€‚
### 五ã€Span/Memory 使用约æŸ
1. `GetSpan()` / `GetMemory()` åªèƒ½åœ¨æ–¹æ³•åŒæ¥æ ˆå¸§å†…使用。
2. ä¸è¦å°† `Span<Byte>` 或 `Memory<Byte>` å˜å…¥å—æ®µã€æ•获到 `async` 方法é—åŒ…ã€æ”¾å…¥ `Channel`/`Queue` 或作为 `Task` 的完æˆå€¼ã€‚
3. è·¨å±‚ä¼ é€’æ•°æ®æ—¶åº”ä¼ é€’ `IPacket`ï¼ˆè€Œéž `Span`/`Memory`),由接收方在其作用域内自行调用 `GetSpan()`。
4. `MemoryPacket` 底层å¯èƒ½æ¥è‡ª `MemoryPool`ï¼Œæœ‰æ•ˆæœŸç”±è°ƒç”¨æ–¹æŽ§åˆ¶ï¼›ä¸æ¯” `Span` 宽æ¾ã€‚
### å…ã€è°ƒè¯•与诊æ–
- `ToHex(maxLength, separator, groupSize)`:打å°åå…进制预览(跨链连ç»åˆ†ç»„)。
- `ToStr(encoding, offset, count)`:按编ç è¯»å–æ–‡æœ¬å†…容。
- `Clone()`:深拷è´å½“å‰é“¾ä¸ºç‹¬ç«‹ `ArrayPacket`,用于æ–点观察数æ®å¿«ç…§ã€‚
### 七ã€é›¶åˆ†é…è§£æžï¼ˆSpanReader)
`SpanReader` 是 `ref struct`,从 `Span<Byte>` / `IPacket` / `Stream` 零分é…读å–二进制数æ®ï¼š
```csharp
// 从 IPacket æž„é€ ï¼Œç›´æŽ¥åœ¨åŽŸå§‹å†…å˜ä¸Šè§£æž
var reader = new SpanReader(packet);
// 读å–基础类型(默认大端,IsLittleEndian=true 切æ¢å°ç«¯ï¼‰
var magic = reader.ReadUInt32();
var length = reader.ReadEncodedInt(); // 7ä½å˜é•¿æ•´æ•°
var name = reader.ReadString(0); // 带长度å‰ç¼€çš„å—符串(长度=0表示读å‰ç¼€ï¼‰
var body = reader.ReadBytes(length); // 固定长度切片
// 读å–结构体(直接内å˜å¸ƒå±€ï¼Œé›¶æ‹·è´ï¼‰
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct PacketHeader { ... }
var header = reader.Read<PacketHeader>();
```
**关键约æŸ**:`SpanReader` 是 `ref struct`ï¼Œç¦æ¢å˜å…¥å—段ã€`async` 方法ã€`Task`ã€é˜Ÿåˆ—。
### å…«ã€é›¶åˆ†é…写入(SpanWriter / PooledByteBufferWriter)
```csharp
// å›ºå®šç¼“å†²åŒºå†™å…¥ï¼ˆæ ˆä¸Šåˆ†é…,Size å°äºŽ 1KB 时推è)
Span<Byte> buf = stackalloc Byte[256];
var w = new SpanWriter(buf);
w.Write((UInt32)0x12345678); // 写 Magic
w.WriteEncodedInt(messageId); // 7ä½å˜é•¿æ•´æ•°
w.Write(content, 0); // 带长度å‰ç¼€çš„å—符串
var written = buf[..w.WrittenCount]; // 已写入的切片
// åŠ¨æ€æ± 化写入(大包或ä¸å®šé•¿ï¼Œå®žçް IBufferWriter<Byte>)
using var pw = new PooledByteBufferWriter(initialCapacity: 1024);
var sw = new SpanWriter(pw.GetSpan(256));
sw.Write(payload);
pw.Advance(sw.WrittenCount);
var memory = pw.WrittenMemory; // ReadOnlyMemory<Byte>,零拷è´è¾“出
```
### ä¹ã€TCP 粘包拆包(PacketCodec)
æ¯ä¸ª TCP 连接独立一个 `PacketCodec`,解决粘包/åŠåŒ…问题:
```csharp
// 在 NetSession ä¸
private readonly PacketCodec _codec = new PacketCodec
{
// 从头部读å–完整包长度(返回 0 / è´Ÿæ•° = æ•°æ®ä¸è¶³ï¼‰
GetLength2 = span =>
{
if (span.Length < 4) return 0;
return (span[0] << 24 | span[1] << 16 | span[2] << 8 | span[3]) + 4;
},
MaxCache = 4 * 1024 * 1024, // é˜²æ¢æ¶æ„大包耗尽内å˜
Expire = 5_000, // 5秒内未收到完整包则丢弃残余缓å˜
};
protected override void OnReceive(ReceivedEventArgs e)
{
// 一包å¯èƒ½åŒ…å«å¤šæ¡æ¶ˆæ¯ï¼Œæˆ–ä¸è¶³ä¸€æ¡
foreach (var pk in _codec.Parse(e.Packet))
ProcessMessage(pk); // pk 是一个完整的业务帧
}
protected override void OnDisconnected(String reason)
{
base.OnDisconnected(reason);
_codec.Dispose(); // 释放内部 MemoryStream
}
```
**快速路径**ï¼šæ— æ®‹ä½™ç¼“å˜æ—¶ï¼Œ`Parse` 直接在原始 `IPacket` 上切片,ä¸åˆ†é…内å˜ï¼›**慢速路径**ï¼šæœ‰æ®‹ä½™ç¼“å˜æ—¶åˆå¹¶åˆ° `MemoryStream` åŽç»§ç»è§£æžï¼ˆæœ‰é”ä¿æŠ¤ï¼‰ã€‚
## é‡ç‚¹æ£€æŸ¥é¡¹
- [ ] `OwnerPacket` 的释放路径是å¦å®Œæ•´ï¼Ÿæ˜¯å¦æœ‰ `using` æˆ–ç‰æ•ˆçš„ `Dispose` ç¡®ä¿å½’è¿˜æ± ï¼Ÿ
- [ ] 是å¦å˜åœ¨å¯¹åŒä¸€ `OwnerPacket` 多次调用默认 `Slice`(å³å¤šæ¬¡é»˜è®¤è½¬ç§»æ‰€æœ‰æƒï¼‰ï¼Ÿ
- [ ] 是å¦å°† `GetSpan()` / `GetMemory()` 的返回值å˜å…¥äº†å—段或 `async` 方法?
- [ ] `MemoryPacket` 是å¦åœ¨æœ‰ `Next` 的情况下调用了 `Slice`(将抛 `NotSupportedException`)?
- [ ] 链䏿˜¯å¦æ··åˆäº† `ArrayPacket` å’Œéž `ArrayPacket` ç±»åž‹ï¼ŒåŒæ—¶å˜åœ¨è·¨æ®µåˆ‡ç‰‡è·¯å¾„?
- [ ] 是å¦é”™è¯¯åœ°è°ƒç”¨ `Free()` 替代 `Dispose()`,导致缓冲区泄æ¼ï¼Ÿ
- [ ] 链å¼éåŽ†æ˜¯å¦æ˜¯æ€§èƒ½çƒè·¯å¾„?若是,考虑æå‰èšåˆä¸ºè¿žç»ç¼“冲区(`ToArray()` / `GetStream()`)。
## è¾“å‡ºè¦æ±‚
- **接å£**:`IPacket.cs`ï¼ˆå« `IOwnerPacket`ï¼‰ï¼Œå®šä¹‰åˆ‡ç‰‡ã€æ‰€æœ‰æƒã€é“¾å¼ã€Span/Memory 方法ç¾å。
- **实现**:按场景分类:`ArrayPacket`(值类型)ã€`OwnerPacket`ï¼ˆæ± åŒ–ï¼‰ã€`MemoryPacket`(外部内å˜ï¼‰ã€`ReadOnlyPacket`(åªè¯»ï¼‰ã€‚
- **扩展**:`PacketHelper.cs`,集ä¸é“¾å¼æ‹¼æŽ¥ã€æ ¼å¼è½¬æ¢ã€æµæ“作ã€å¤´éƒ¨æ‰©å±•ç‰å·¥å…·æ–¹æ³•。
- **å•元测试**:覆盖所有æƒè½¬ç§»ã€å¤šæ¬¡åˆ‡ç‰‡é˜²é‡å¤é‡Šæ”¾ã€è·¨æ®µç´¢å¼•ã€`MemoryPacket + Next` å¼‚å¸¸è·¯å¾„ã€æ± 内å˜å½’还验è¯ã€‚
- **文档**:明确说明å„实现的生æ»å‘¨æœŸã€`Slice` 所有æƒé»˜è®¤è¡Œä¸ºå·®å¼‚ã€é“¾å¼åŒ…的适用范围。
## å‚考资料
å‚考示例与设计决ç–è¯æ®è§ `references/newlife-ipacket-patterns.md`。
|