解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
大石头 authored at 2018-05-15 21:21:05
12.96 KiB
X
# IPacket 性能测试报告 ## 1. 概述 本报告针对 `NewLife.Data` 命名空间中 `IPacket` 接口的四个主要实现者以及 `PacketHelper` 扩展方法进行全面的性能基准测试。 ### 1.1 测试对象 | 类型 | 说明 | 内存管理 | |------|------|----------| | **OwnerPacket** | 基于 `ArrayPool<Byte>.Shared` 的池化内存包 | 需手动 Dispose 归还 | | **MemoryPacket** | 基于 `Memory<Byte>` 的内存包(struct) | 无所有权,不管理内存生命周期 | | **ArrayPacket** | 基于 `Byte[]` 的数组包(record struct) | 无池化,依赖 GC | | **ReadOnlyPacket** | 只读数组包(readonly record struct) | 无池化,不可变,线程安全 | | **PacketHelper** | 扩展方法集:链式操作、数据转换、流操作、数组操作等 | — | ### 1.2 测试框架与环境 - **测试框架**:BenchmarkDotNet v0.15.8 - **运行时**:.NET 8.0 / .NET 9.0 - **诊断器**:MemoryDiagnoser(测量 GC 分配与回收) - **数据大小**:64B / 1KB / 64KB(覆盖小包、中包、大包场景) - **并发线程**:1 / 4 / 16 / 32 线程 ## 2. 测试项目结构 ``` Benchmark/ ├── Benchmark.csproj # 项目文件 ├── Program.cs # 入口,配置 MemoryDiagnoser └── PacketBenchmarks/ ├── OwnerPacketBenchmark.cs # OwnerPacket 独立测试 ├── MemoryPacketBenchmark.cs # MemoryPacket 独立测试 ├── ArrayPacketBenchmark.cs # ArrayPacket 独立测试 ├── ReadOnlyPacketBenchmark.cs # ReadOnlyPacket 独立测试 ├── PacketHelperBenchmark.cs # PacketHelper 扩展方法测试 ├── PacketComparisonBenchmark.cs # 四种实现横向对比 └── PacketConcurrencyBenchmark.cs # 多线程并发测试 ``` ## 3. 运行方式 ### 3.1 运行全部基准测试 ```bash dotnet run --project Benchmark/Benchmark.csproj -f net8.0 -c Release -- --filter "*" ``` ### 3.2 运行单个测试类 ```bash # OwnerPacket 测试 dotnet run --project Benchmark/Benchmark.csproj -f net8.0 -c Release -- --filter "*OwnerPacketBenchmark*" # MemoryPacket 测试 dotnet run --project Benchmark/Benchmark.csproj -f net8.0 -c Release -- --filter "*MemoryPacketBenchmark*" # ArrayPacket 测试 dotnet run --project Benchmark/Benchmark.csproj -f net8.0 -c Release -- --filter "*ArrayPacketBenchmark*" # ReadOnlyPacket 测试 dotnet run --project Benchmark/Benchmark.csproj -f net8.0 -c Release -- --filter "*ReadOnlyPacketBenchmark*" # PacketHelper 测试 dotnet run --project Benchmark/Benchmark.csproj -f net8.0 -c Release -- --filter "*PacketHelperBenchmark*" # 横向对比测试 dotnet run --project Benchmark/Benchmark.csproj -f net8.0 -c Release -- --filter "*PacketComparisonBenchmark*" # 并发测试 dotnet run --project Benchmark/Benchmark.csproj -f net8.0 -c Release -- --filter "*PacketConcurrencyBenchmark*" ``` ### 3.3 列出所有可用基准 ```bash dotnet run --project Benchmark/Benchmark.csproj -f net8.0 -c Release -- --list flat ``` ### 3.4 导出结果 BenchmarkDotNet 默认会在项目根目录生成 `BenchmarkDotNet.Artifacts/` 文件夹,包含: - Markdown 格式的结果表格 - CSV 格式的原始数据 - 日志文件 ## 4. 测试用例详解 ### 4.1 OwnerPacket 基准测试(11 项 × 3 数据大小) | 测试方法 | 说明 | 关注指标 | |---------|------|---------| | 构造_从池租用 | `new OwnerPacket(size)` + `Dispose()` | 池化租借/归还耗时 | | 构造_包装数组 | `new OwnerPacket(buf, 0, len, false)` | 零拷贝包装开销 | | GetSpan | 获取 Span 视图 | 内存访问延迟 | | GetMemory | 获取 Memory 视图 | 内存访问延迟 | | TryGetArray | 获取 ArraySegment | 数组段提取开销 | | Slice_不转移所有权 | 切片,共享缓冲区 | 零拷贝切片 | | Slice_转移所有权 | 切片,转移管理权 | 所有权转移开销 | | 索引器读取 | `pk[index]` 读取 | 随机读性能 | | 索引器写入 | `pk[index] = value` 写入 | 随机写性能 | | Resize | 调整有效长度 | 大小调整开销 | | Dispose归还池 | `using` 释放 | 池归还延迟 | ### 4.2 MemoryPacket 基准测试(8 项 × 3 数据大小) | 测试方法 | 说明 | 关注指标 | |---------|------|---------| | 构造 | `new MemoryPacket(memory, len)` | 构造开销 | | GetSpan | 获取 Span 视图 | Span 切片延迟 | | GetMemory | 获取 Memory 视图 | Memory 切片延迟 | | TryGetArray | 获取 ArraySegment | Marshal 提取开销 | | Slice | 切片操作 | 零拷贝切片 | | 索引器读取 | 字节读取 | Memory.Span 访问 | | 索引器写入 | 字节写入 | Memory.Span 访问 | | Total属性 | 计算总长度 | 链式遍历开销 | ### 4.3 ArrayPacket 基准测试(12 项 × 3 数据大小) | 测试方法 | 说明 | 关注指标 | |---------|------|---------| | 构造_字节数组 | `new ArrayPacket(buf)` | 最轻量构造 | | 构造_ArraySegment | `new ArrayPacket(segment)` | 段构造开销 | | 构造_偏移 | `new ArrayPacket(buf, off, cnt)` | 带偏移构造 | | GetSpan | 获取 Span 视图 | 直接数组切片 | | GetMemory | 获取 Memory 视图 | Memory 构造 | | TryGetArray | 获取 ArraySegment | ArraySegment 构造 | | Slice | 切片操作 | record struct 复制 | | 索引器读取/写入 | 字节访问 | 数组直接访问 | | Total属性 | 计算总长度 | 属性访问 | | 隐式转换_字节数组 | `byte[]` → `ArrayPacket` | 隐式转换开销 | | 隐式转换_字符串 | `string` → `ArrayPacket` | 含 GetBytes 分配 | ### 4.4 ReadOnlyPacket 基准测试(12 项 × 3 数据大小) | 测试方法 | 说明 | 关注指标 | |---------|------|---------| | 构造_字节数组 | 零拷贝包装 | 构造开销 | | 构造_ArraySegment | 段构造 | 构造开销 | | 构造_偏移 | 带偏移构造 | 参数校验开销 | | 构造_从IPacket深拷贝 | `new ReadOnlyPacket(IPacket)` | 深拷贝分配 | | GetSpan | Span 视图 | 只读视图 | | GetMemory | Memory 视图 | 只读视图 | | TryGetArray | ArraySegment | 直接返回 | | Slice | 切片 | readonly struct 复制 | | 索引器读取 | 含边界检查 | 安全读取 | | Total属性 | 直接返回 Length | 无链式遍历 | | ToArray | 字节数组副本 | 可能零拷贝 | | 隐式转换 | `byte[]` → `ReadOnlyPacket` | 转换开销 | ### 4.5 PacketHelper 扩展方法基准测试(21 项 × 3 数据大小) | 分类 | 测试方法 | 说明 | |------|---------|------| | **链式操作** | Append_IPacket | 追加 IPacket 到链尾 | | | Append_ByteArray | 追加字节数组到链尾 | | **数据转换** | ToStr_单包 | 单包 UTF-8 转字符串 | | | ToStr_链式包 | 链式包转字符串(StringBuilder 池化) | | | ToHex_单包 | 转十六进制(32 字节) | | | ToHex_带分隔符 | 转十六进制(含分隔符) | | | ToHex_链式包 | 链式包转十六进制 | | **流操作** | CopyTo | 复制到 MemoryStream | | | GetStream_单包 | 获取 MemoryStream 视图 | | | GetStream_链式包 | 链式包获取流 | | **数据段** | ToSegment_单包 | 单包获取 ArraySegment | | | ToSegment_链式包 | 链式包聚合到新数组 | | | ToSegments | 获取分段列表 | | | ToArray_单包 | 转字节数组 | | | ToArray_链式包 | 链式包聚合转数组 | | **数据读取** | ReadBytes_全部 | 读取全部字节 | | | ReadBytes_切片 | 读取部分字节 | | | Clone | 深拷贝 | | | Clone_链式包 | 链式包深拷贝 | | **内存访问** | TryGetSpan_单包 | 尝试获取 Span(成功) | | | TryGetSpan_链式包 | 尝试获取 Span(失败) | | **头部扩展** | ExpandHeader_ArrayPacket有空间 | 原地扩展 | | | ExpandHeader_创建新包 | 创建新 OwnerPacket | | | ExpandHeader_OwnerPacket有空间 | 原地扩展 | ### 4.6 横向对比基准测试(24 项 × 3 数据大小) 将四种实现在**相同操作**下进行横向对比,覆盖: - **构造**:OwnerPacket(含池化)vs MemoryPacket vs ArrayPacket vs ReadOnlyPacket - **GetSpan**:各实现的 Span 获取性能 - **GetMemory**:各实现的 Memory 获取性能 - **Slice**:各实现的切片性能 - **TryGetArray**:各实现的数组段获取性能 - **Indexer**:各实现的索引器访问性能 ### 4.7 多线程并发基准测试(20 项 × 4 线程数) 每个测试在 1/4/16/32 个线程下运行,每线程执行 1000 次操作。覆盖: | 分类 | 测试项目 | 并发关注点 | |------|---------|-----------| | OwnerPacket | 构造与释放 | ArrayPool 线程安全性能 | | | GetSpan | 并发读性能 | | | Slice | 并发切片性能 | | ArrayPacket | 构造 | struct 构造无竞争 | | | GetSpan | 并发 Span 创建 | | | Slice | 并发切片 | | | ToArray | 并发数组拷贝与 GC 压力 | | MemoryPacket | 构造 | struct 构造 | | | GetSpan | 并发 Span 创建 | | | Slice | 并发切片 | | ReadOnlyPacket | 构造 | readonly struct 构造 | | | GetSpan | 并发只读 Span | | | Slice | 并发只读切片 | | PacketHelper | ToStr | 并发字符串转换与 StringBuilder 池竞争 | | | ToHex | 并发十六进制转换 | | | Clone | 并发深拷贝与 GC 压力 | | | ReadBytes | 并发字节读取 | | | ToSegment | 并发段获取 | ## 5. 预期性能特征分析 ### 5.1 构造性能 | 实现 | 预期开销 | 原因 | |------|---------|------| | **ArrayPacket** | ⭐ 最快 | record struct,仅赋值字段 | | **ReadOnlyPacket** | ⭐ 最快 | readonly record struct,含参数校验 | | **MemoryPacket** | ⭐ 接近最快 | struct,含参数校验 | | **OwnerPacket** | ⚡ 较慢 | 需要从 ArrayPool 租借缓冲区 | ### 5.2 内存分配 | 实现 | GC 分配 | 原因 | |------|--------|------| | **ArrayPacket** | 零分配 | struct,共享原数组 | | **ReadOnlyPacket** | 零分配 | struct,共享原数组 | | **MemoryPacket** | 零分配 | struct,共享 Memory | | **OwnerPacket** | 有分配 | class 实例 + ArrayPool.Rent | ### 5.3 GetSpan / GetMemory 所有实现均为 O(1) 操作,预期差异极小: - **ArrayPacket / ReadOnlyPacket**:直接构造 `new Span<Byte>(buf, off, len)` - **MemoryPacket**:通过 `Memory.Span` 切片 - **OwnerPacket**:同 ArrayPacket ### 5.4 Slice 切片 | 实现 | 特点 | |------|------| | **ArrayPacket** | 零拷贝,返回新 record struct | | **ReadOnlyPacket** | 零拷贝,返回新 readonly record struct | | **MemoryPacket** | 零拷贝,Memory 切片 | | **OwnerPacket** | 零拷贝,可能伴随 new class 实例 | ### 5.5 并发扩展性 | 实现 | 并发预期 | |------|---------| | **ArrayPacket** | 线性扩展,无共享状态 | | **ReadOnlyPacket** | 线性扩展,不可变 | | **MemoryPacket** | 线性扩展,无共享状态 | | **OwnerPacket** | 受 ArrayPool 锁竞争影响 | | **PacketHelper.ToStr** | 受 StringBuilder 池竞争影响 | ## 6. 使用建议 ### 6.1 选型指南 | 场景 | 推荐实现 | 理由 | |------|---------|------| | 网络收发缓冲区 | **OwnerPacket** | 池化复用,减少大量 GC | | 协议解析临时切片 | **ArrayPacket** | 零分配,高吞吐 | | 多线程共享只读数据 | **ReadOnlyPacket** | 不可变,天然线程安全 | | Memory/IMemoryOwner 适配 | **MemoryPacket** | 适配 Memory-based API | | 一次性数据传递 | **ArrayPacket** | 最轻量级 | ### 6.2 性能优化建议 1. **避免不必要的 ToArray**:优先使用 `GetSpan()` / `GetMemory()` 避免数组拷贝 2. **合理使用所有权转移**:`Slice(offset, count, transferOwner: true)` 避免重复释放 3. **利用单包快速路径**:PacketHelper 的大多数方法对单包(无 Next)有专门的快速路径优化 4. **控制链式包长度**:过长的链式结构会导致遍历开销增大 5. **注意 OwnerPacket 的及时释放**:未及时 Dispose 会导致 ArrayPool 碎片化 ## 7. 测试覆盖统计 | 类别 | 测试类 | 方法数 | 参数组合 | 合计用例 | |------|--------|--------|---------|---------| | 独立测试 | OwnerPacketBenchmark | 11 | × 3 | 33 | | 独立测试 | MemoryPacketBenchmark | 8 | × 3 | 24 | | 独立测试 | ArrayPacketBenchmark | 12 | × 3 | 36 | | 独立测试 | ReadOnlyPacketBenchmark | 12 | × 3 | 36 | | 扩展方法 | PacketHelperBenchmark | 21 | × 3 | 63 | | 横向对比 | PacketComparisonBenchmark | 24 | × 3 | 72 | | 并发测试 | PacketConcurrencyBenchmark | 20 | × 4 | 80 | | **合计** | **7 个测试类** | **108** | | **344** | ## 8. 附录 ### 8.1 数据大小选择依据 | 大小 | 典型场景 | |------|---------| | 64 B | 小型控制指令、心跳包、短消息 | | 1 KB | 常规 RPC 请求/响应、JSON 消息 | | 64 KB | 文件传输分片、大型序列化对象 | ### 8.2 并发线程数选择依据 | 线程数 | 场景 | |--------|------| | 1 | 基线单线程性能 | | 4 | 典型小型服务器 | | 16 | 中型服务器 | | 32 | 高并发服务器 | ### 8.3 关键指标说明 | 指标 | 说明 | |------|------| | Mean | 平均执行时间 | | Error | 99.9% 置信区间的一半 | | StdDev | 标准差 | | Gen0/Gen1/Gen2 | 各代 GC 收集次数 | | Allocated | 每次操作分配的托管内存 |