解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
大石头 authored at 2018-05-15 21:21:05
14.30 KiB
X
# SpanSerializer vs Binary 序列化性能测试 ## 性能概览 `SpanSerializer` 是基于 `SpanReader`/`SpanWriter` 的零流、无处理器链高性能序列化器,适用于 RPC 通信和文件读写。 测试结论:**SpanSerializer(反射路径) 比 Binary 快 9~21 倍;实现 `ISpanSerializable` 接口后可再快 2.5~4 倍,且零堆分配。** --- ## 测试环境 | 项目 | 值 | |------|-----| | CPU | Intel Core i9-10900K @ 3.70 GHz(20 逻辑核心)| | OS | Windows 10 22H2 (19045.6456) | | .NET | .NET 10.0 | | BenchmarkDotNet | v0.15.8 | | 编译模式 | Release | --- ## 测试方法 三条序列化路径: | 路径 | 说明 | |------|------| | **SpanSerializer(反射)** | 普通 POCO,通过编译委托(`Expression.Lambda`)序列化 | | **SpanSerializer(ISpanSerializable)** | 实现接口,零反射手写路径 | | **Binary** | 基于 `MemoryStream` + 处理器链的传统序列化 | 测试模型: - `SimpleModel`:5 个字段(Int32/String/Boolean/DateTime/Double) - `NestedModel`:4 个字段 + 内嵌 SimpleModel - `FastModel`:同 SimpleModel,但实现 `ISpanSerializable` 测试维度:单次操作(序列化/反序列化)、批量操作(100 / 1000 条)、多线程并发(1 / 4 / 8 / 20 / 32 线程)。 --- ## 测试结果 ### 1. 单次序列化 > 基准(Baseline)= `Span_Simple序列化_到Span` | 方法 | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio | |------|-----:|------:|-------:|------:|-----:|----------:|------------:| | Span_Simple序列化_到Span *(Baseline)* | 59.03 ns | 0.432 ns | 0.067 ns | 1.00 | 0.0092 | 96 B | 1.00 | | Span_Simple序列化_池化包 | 64.80 ns | 1.473 ns | 0.382 ns | 1.10 | 0.0092 | 96 B | 1.00 | | Span_Nested序列化_到Span | 110.56 ns | 1.166 ns | 0.303 ns | 1.87 | 0.0137 | 144 B | 1.50 | | **Span_Fast序列化_到Span** | **23.49 ns** | 0.113 ns | 0.029 ns | **0.40** | - | **0 B** | **0.00** | | Span_Fast序列化_池化包 | 29.26 ns | 0.059 ns | 0.015 ns | 0.50 | - | 0 B | 0.00 | | Binary_Simple序列化 | 800.83 ns | 15.063 ns | 3.912 ns | 13.57 | 0.1926 | 2016 B | 21.00 | | Binary_Nested序列化 | 1,222.42 ns | 19.373 ns | 2.998 ns | 20.71 | 0.2632 | 2752 B | 28.67 | ### 2. 单次反序列化 > 基准(Baseline)= `Span_Simple反序列化` | 方法 | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio | |------|-----:|------:|-------:|------:|-----:|----------:|------------:| | Span_Simple反序列化 *(Baseline)* | 111.96 ns | 1.599 ns | 0.415 ns | 1.00 | 0.0175 | 184 B | 1.00 | | Span_Nested反序列化 | 219.83 ns | 2.998 ns | 0.464 ns | 1.96 | 0.0312 | 328 B | 1.78 | | **Span_Fast反序列化** | **75.72 ns** | 0.726 ns | 0.189 ns | **0.68** | 0.0083 | **88 B** | **0.48** | | Binary_Simple反序列化 | 1,008.17 ns | 14.916 ns | 3.874 ns | 9.00 | 0.2270 | 2392 B | 13.00 | | Binary_Nested反序列化 | 1,747.46 ns | 10.144 ns | 2.634 ns | 15.61 | 0.3204 | 3360 B | 18.26 | ### 3. 批量操作(100 / 1000 条 SimpleModel) > 基准(Baseline)= `Span_批量序列化`(同 Count 分组) | Count | 方法 | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio | |------:|------|-----:|------:|-------:|------:|-----:|----------:|------------:| | 100 | Span_批量序列化 *(Baseline)* | 5.497 μs | 0.100 μs | 0.016 μs | 1.00 | 0.9155 | 9.38 KB | 1.00 | | 100 | Span_批量反序列化 | 10.695 μs | 0.191 μs | 0.050 μs | 1.95 | 1.7548 | 17.97 KB | 1.92 | | 100 | Binary_批量序列化 | 55.637 μs | 0.552 μs | 0.143 μs | 10.12 | 9.2773 | 94.89 KB | 10.12 | | 100 | Binary_批量反序列化 | 76.196 μs | 0.806 μs | 0.209 μs | 13.86 | 10.254 | 105.83 KB | 11.29 | | 1000 | Span_批量序列化 *(Baseline)* | 55.577 μs | 0.681 μs | 0.177 μs | 1.00 | 9.1553 | 93.75 KB | 1.00 | | 1000 | Span_批量反序列化 | 109.082 μs | 0.975 μs | 0.253 μs | 1.96 | 17.578 | 179.69 KB | 1.92 | | 1000 | Binary_批量序列化 | 576.703 μs | 15.012 μs | 3.899 μs | 10.38 | 91.797 | 938.71 KB | 10.01 | | 1000 | Binary_批量反序列化 | 783.185 μs | 35.201 μs | 9.142 μs | 14.09 | 102.54 | 1048.09 KB | 11.18 | ### 4. 多线程并发(SimpleModel 序列化/反序列化) > 基准(Baseline)= `Span_并发序列化`(同 ThreadCount 分组) | ThreadCount | 方法 | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio | |------------:|------|-----:|------:|-------:|------:|-----:|----------:|------------:| | 1 | Span_并发序列化 *(Baseline)* | 2.621 μs | 0.043 μs | 0.011 μs | 1.00 | 0.1564 | 1.63 KB | 1.00 | | 1 | Span_并发反序列化 | 2.624 μs | 0.030 μs | 0.008 μs | 1.00 | 0.1678 | 1.71 KB | 1.05 | | 1 | Binary_并发序列化 | 2.660 μs | 0.023 μs | 0.004 μs | 1.01 | 0.3738 | 3.84 KB | 2.36 | | 1 | Binary_并发反序列化 | 2.617 μs | 0.090 μs | 0.023 μs | 1.00 | 0.3662 | 3.88 KB | 2.38 | | 4 | Span_并发序列化 *(Baseline)* | 2.584 μs | 0.039 μs | 0.010 μs | 1.00 | 0.1984 | 2.07 KB | 1.00 | | 4 | Span_并发反序列化 | 2.635 μs | 0.021 μs | 0.006 μs | 1.02 | 0.2365 | 2.42 KB | 1.17 | | 4 | Binary_并发序列化 | 4.896 μs | 0.096 μs | 0.025 μs | 1.89 | 1.0986 | 11.23 KB | 5.42 | | 4 | Binary_并发反序列化 | 5.681 μs | 0.100 μs | 0.026 μs | 2.20 | 1.0986 | 11.36 KB | 5.48 | | 8 | Span_并发序列化 *(Baseline)* | 2.667 μs | 0.110 μs | 0.028 μs | 1.00 | 0.2594 | 2.67 KB | 1.00 | | 8 | Span_并发反序列化 | 2.768 μs | 0.076 μs | 0.020 μs | 1.04 | 0.3204 | 3.36 KB | 1.26 | | 8 | Binary_并发序列化 | 7.096 μs | 0.241 μs | 0.063 μs | 2.66 | 2.0447 | 20.93 KB | 7.82 | | 8 | Binary_并发反序列化 | 8.651 μs | 0.188 μs | 0.049 μs | 3.24 | 2.0752 | 21.23 KB | 7.94 | | 20 | Span_并发序列化 *(Baseline)* | 3.726 μs | 0.098 μs | 0.026 μs | 1.00 | 0.4425 | 4.59 KB | 1.00 | | 20 | Span_并发反序列化 | 4.750 μs | 0.121 μs | 0.031 μs | 1.27 | 0.6104 | 6.32 KB | 1.37 | | 20 | Binary_并发序列化 | 12.809 μs | 0.265 μs | 0.069 μs | 3.44 | 4.9133 | 49.94 KB | 10.87 | | 20 | Binary_并发反序列化 | 13.206 μs | 0.919 μs | 0.239 μs | 3.54 | 4.5929 | 46.67 KB | 10.16 | | 32 | Span_并发序列化 *(Baseline)* | 5.248 μs | 0.050 μs | 0.013 μs | 1.00 | 0.6256 | 6.40 KB | 1.00 | | 32 | Span_并发反序列化 | 6.777 μs | 0.209 μs | 0.054 μs | 1.29 | 0.8850 | 9.28 KB | 1.45 | | 32 | Binary_并发序列化 | 16.732 μs | 0.138 μs | 0.021 μs | 3.19 | 7.7515 | 78.91 KB | 12.32 | | 32 | Binary_并发反序列化 | 18.248 μs | 0.301 μs | 0.078 μs | 3.48 | 7.8735 | 79.96 KB | 12.49 | --- ## 核心指标 > 以下吞吐量基于单线程单次操作均值换算(ops/ms = 1,000,000 ns ÷ Mean ns) | 操作 | SpanSerializer(反射) | SpanSerializer(ISpanSerializable) | Binary | |------|---------------------:|----------------------------------:|-------:| | 序列化 SimpleModel | ~16,940,000 ops/s | ~42,570,000 ops/s | ~1,249,000 ops/s | | 反序列化 SimpleModel | ~8,932,000 ops/s | ~13,206,000 ops/s | ~992,000 ops/s | | 批量序列化(1000条)| ~17,994,000 ops/s | — | ~1,735,000 ops/s | | 批量反序列化(1000条)| ~9,167,000 ops/s | — | ~1,277,000 ops/s | --- ## 对比分析 ### 纵向:多线程并发趋势 `SpanSerializer` 并发序列化耗时从 1 线程的 2.62 μs 增长到 32 线程的 5.25 μs,增幅约 **100%**,说明 `Parallel.For` 和线程调度本身带来了固定开销,但实际吞吐随线程增加线性扩展。 `Binary` 并发序列化从 1 线程 2.66 μs 增长到 32 线程 16.73 μs,增幅约 **529%**,主要原因是每次调用需创建 `MemoryStream`,高并发下 GC 压力显著放大(32 线程内存分配达 78.91 KB/op,是 SpanSerializer 的 12.3 倍)。 | ThreadCount | SpanSerializer序列化 | Binary序列化 | 差距倍数 | |------------:|--------------------:|------------:|--------:| | 1 | 2.62 μs | 2.66 μs | ≈1.0× | | 4 | 2.58 μs | 4.90 μs | 1.9× | | 8 | 2.67 μs | 7.10 μs | 2.7× | | 20 | 3.73 μs | 12.81 μs | 3.4× | | 32 | 5.25 μs | 16.73 μs | 3.2× | > 单线程下两者耗时接近(Parallel.For 本身有固定开销),**4 线程以上 SpanSerializer 优势持续扩大**。 ### 横向:各场景速度与内存对比 | 场景 | SpanSerializer(反射) 速度优势 | SpanSerializer 内存节省 | |------|-----------------------------:|------------------------:| | 单次序列化 Simple | **13.6×** 快 | 95.2%(96 B vs 2016 B)| | 单次序列化 Nested | **11.1×** 快 | 94.8%(144 B vs 2752 B)| | 单次反序列化 Simple | **9.0×** 快 | 92.3%(184 B vs 2392 B)| | 单次反序列化 Nested | **7.9×** 快 | 90.2%(328 B vs 3360 B)| | 批量序列化 1000 条 | **10.4×** 快 | 90.0%(93.75 KB vs 938.71 KB)| | 批量反序列化 1000 条 | **7.2×** 快 | 82.8%(179.69 KB vs 1048.09 KB)| | ISpanSerializable 序列化 | **34.1×** 快 | 100%(0 B vs 2016 B)| | ISpanSerializable 反序列化 | **13.3×** 快 | 96.3%(88 B vs 2392 B)| --- ## 性能瓶颈定位 ### 核心瓶颈点总览 | 优先级 | 瓶颈 | 优化收益占比 | 当前开销 | 优化后预估 | 内存节省 | |--------|------|------------|---------|-----------|---------| | P0 | Binary MemoryStream 每次重建 | ~35% | 800 ns / 2,016 B(Simple 序列化) | 迁移至 SpanSerializer:59 ns / 96 B | **95.2%(1,920 B/op)** | | P0 | Binary 处理器链逐 handler 匹配 | ~25% | 每字段 2-3 handler 遍历,~400 ns 处理器开销 | 迁移至 SpanSerializer 消除 | **100%** | | P1 | SpanSerializer 反射路径值类型装箱 | ~20% | 每属性 1 次 box/unbox,反序列化 112 ns(vs ISpanSerializable 76 ns) | 泛型重写消除装箱:~80-90 ns | **30-40%** | | P1 | SpanSerializer CreateInstance 反射 | ~10% | 反序列化 112 ns(含构造),比序列化 59 ns 慢 1.9x | 缓存构造函数委托:~90-100 ns | **10-20%** | | P2 | Binary 高并发 GC 压力放大 | ~5% | 32T 序列化 78.91 KB/op(SpanSerializer 仅 6.40 KB) | 迁移至 SpanSerializer | **91.9%** | | P3 | SpanSerializer 反序列化 String 分配 | ~5% | 反序列化 184 B/op(含 String 堆分配) | 使用 Span<Char> 零拷贝:88 B/op | **52.2%** | ### 关键内存优化方向 | 优先级 | 优化方向 | 当前分配 | 优化后预估 | 节省比例 | 实施方案 | |--------|---------|---------|-----------|---------|---------| | P0 | Binary → SpanSerializer 迁移 | 2,016 B/op(序列化) | 96 B/op | **95.2%** | 频繁序列化的数据结构改用 SpanSerializer | | P0 | Binary → ISpanSerializable 迁移 | 2,016 B/op(序列化) | 0 B/op | **100%** | 核心数据结构实现 `ISpanSerializable` 接口 | | P1 | 反射路径值类型装箱消除 | 96 B/op → 含装箱开销 | ~60 B/op | **~37%** | 泛型重写 `WriteValue`/`ReadValue` | | P2 | Binary 并发场景 MemoryStream 复用 | 78.91 KB/op(32T) | ~10 KB/op | **87%** | `ArrayPool` + 重置 Position | ### 瓶颈 1(P0):Binary 序列化器 MemoryStream 重建 - **现象**:Binary SimpleModel 序列化 800.83 ns / 2,016 B,是 SpanSerializer(59.03 ns / 96 B)的 **13.6x 慢、21x 内存** - **根源**:每次 `Binary.Write` 创建 `new MemoryStream(256)`,初始分配 256 字节数组 + MemoryStream 对象(~200 ns) - **影响**:高并发 32 线程下内存达 78.91 KB/op,GC Gen0 压力是 SpanSerializer 的 12.3 倍 | 开销来源 | 占比估算 | 耗时估算 | 说明 | |---------|---------|---------|------| | MemoryStream 构造 + 数组分配 | ~25% | ~200 ns | `new MemoryStream(256)` 堆分配 | | 处理器链遍历 | ~50% | ~400 ns | `foreach Handlers` 逐 handler 匹配字段 | | 缓冲区扩容 + 数据写入 | ~25% | ~200 ns | 字段数据写入 + 可能的数组扩容 | - **优化方案**:将频繁序列化的核心数据结构迁移至 SpanSerializer - **预期收益**:速度提升 **13.6x**,内存降低 **95.2%** ### 瓶颈 2(P1):SpanSerializer 反射路径值类型装箱 - **现象**:反序列化 111.96 ns / 184 B,比 ISpanSerializable(75.72 ns / 88 B)慢 **1.5x** - **根源**:`Getter/Setter` 委托通过 `Object?` 传递值类型,每属性产生一次 box/unbox - **影响**:5 字段的 SimpleModel 序列化时产生 5 次装箱 | 开销来源 | 占比估算 | 耗时估算 | 说明 | |---------|---------|---------|------| | 装箱(Object? 传递值类型) | ~30% | ~18 ns | 5 字段 × ~3.5 ns/次装箱 | | Getter 委托调用 | ~40% | ~24 ns | 5 字段 × 编译缓存委托调用 | | 类型判断 + Span 写入 | ~30% | ~17 ns | GetSchema 类型匹配 + writer.Write | - **优化方案**:泛型重写 `WriteValue<T>`/`ReadValue<T>`,消除值类型装箱 - **预期收益**:反射路径再提速 **20-40%** ### 瓶颈 3(P1):SpanSerializer 反序列化 CreateInstance - **现象**:反序列化(111.96 ns)比序列化(59.03 ns)慢 **1.9x** - **根源**:需 `CreateInstance` 构造新对象 + `Setter` 写回字段,比序列化多一步对象创建 - **影响**:每次反序列化额外 ~10-20 ns 用于对象构造 - **优化方案**:缓存默认构造函数的委托(`Activator.CreateInstance` → 编译 `Expression.New`) - **预期收益**:反序列化提速 **10-20%** ### SpanSerializer 反射路径 vs ISpanSerializable 对比 | 维度 | 反射路径 | ISpanSerializable | 差距 | |------|---------|-------------------|------| | 序列化耗时 | 59.03 ns | 23.49 ns | 2.5x | | 反序列化耗时 | 111.96 ns | 75.72 ns | 1.5x | | 序列化分配 | 96 B | 0 B | ∞ | | 反序列化分配 | 184 B | 88 B | 2.1x | | 维护成本 | 零(自动) | 高(手写 Write/Read) | — | --- ## 优化建议 | 优先级 | 方向 | 预期收益 | 实施方案 | |--------|------|---------|---------| | P0 ★★★ | **核心数据结构实现 `ISpanSerializable`** | 序列化再快 **2.5x**,内存降至 **0 B** | 频繁序列化的 RPC 消息体实现接口,获得零反射+零分配路径 | | P1 ★★☆ | **泛型重写 `WriteValue`/`ReadValue`** | 反射路径再提速 **20-40%**,消除值类型装箱 | 用 `WriteValue<T>` 替代 `WriteValue(Object?)`,避免每属性 box/unbox | | P1 ★★☆ | **缓存 `CreateInstance` 构造函数委托** | 反序列化提速 **10-20%** | 编译 `Expression.New` 并缓存,避免每次反射构造 | | P2 ★☆☆ | **Binary 场景复用 MemoryStream** | 高并发内存降低 **80%** | `ArrayPool` + 重置 Position 替代每次 `new MemoryStream(256)` | | P3 ★☆☆ | **支持 `Span<T>` 属性自动序列化** | 扩大 ISpanSerializable 适用范围,减少手写量 | 列表/数组属性的自动 Span 序列化支持 |