在需要低延迟、高吞吐量的系统中(如金融交易引擎、游戏服务器、实时数据处理),遵循高性能编码规范至关重要。以下是关键原则及实战示例:
一、核心开发规范
-
内存分配控制
- 优先使用值类型(
struct
),减少堆内存分配 - 避免装箱操作
- 使用
ArrayPool<T>
重用数组 - 使用
stackalloc
栈上分配小内存块
- 优先使用值类型(
-
集合优化
- 预分配集合容量:
List<T>(capacity)
- 使用
Span<T>
和Memory<T>
进行无拷贝数据操作 - 优先选择
Dictionary
而非LINQ
的.First()
/.Where()
- 预分配集合容量:
-
异步编程
- 使用
ValueTask
代替Task
减少分配 - 避免
async void
,推荐async Task
- 使用
IValueTaskSource
实现零分配异步
- 使用
-
算法与数据布局
- 确保数据结构连续内存布局:
[StructLayout(LayoutKind.Sequential)]
- 利用 SIMD 指令:
System.Numerics.Vector<T>
- 优化分支预测:避免虚方法调用
- 确保数据结构连续内存布局:
二、实战代码示例
1. 栈上分配减少 GC 压力
// 处理小型临时数据(不超过 128 字节)
unsafe void ProcessData(ReadOnlySpan<byte> input) {
const int MaxSize = 128;
byte* buffer = stackalloc byte[MaxSize];
Span<byte> tempBuffer = new Span<byte>(buffer, MaxSize);
input.CopyTo(tempBuffer); // 零堆分配操作
// 后续处理...
}
2. 使用 ArrayPool 重用数组
// 避免频繁分配大数组
void ProcessBatch(int batchSize) {
var pool = ArrayPool<int>.Shared;
int[] buffer = pool.Rent(batchSize);
try {
// 使用 buffer 进行操作
for (int i = 0; i < batchSize; i++) buffer[i] *= 2;
}
finally {
pool.Return(buffer); // 显式归还到池
}
}
3. Span<T> 高性能数据切片
// 解析二进制协议(无拷贝)
int ParseHeader(ReadOnlySpan<byte> data) {
// 直接操作内存片段
if (data.Length < 4) return -1;
int id = BinaryPrimitives.ReadInt32LittleEndian(data);
ReadOnlySpan<byte> payload = data.Slice(4, 100); // 不产生新数组
return ProcessPayload(payload);
}
4. 结构化内存布局加速处理
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Particle {
public Vector3 Position; // 连续内存
public float Velocity;
public int TypeId;
}
// 通过固定内存加速物理计算
unsafe void UpdateParticles(Particle[] particles) {
fixed (Particle* ptr = particles) {
for (int i = 0; i < particles.Length; i++) {
ptr[i].Position += ptr[i].Velocity * deltaTime;
}
}
}
5. ValueTask 高并发达
// 实现零分配异步操作
public ValueTask<int> ReadDataAsync(Socket socket) {
if (socket.Available > 0)
return new ValueTask<int>(socket.Receive(_buffer));
else
return WaitForDataAsync(socket); // 自定义异步状态机
}
private async ValueTask<int> WaitForDataAsync(Socket socket) {
await _semaphore.WaitAsync();
return await socket.ReceiveAsync(_buffer, SocketFlags.None);
}
三、性能陷阱规避
场景 | 错误做法 | 优化方案 |
---|---|---|
集合扩容 | 无预分配 List<T> | 指定初始容量 new List<T>(1000) |
字符串拼接 | 多次 += 操作 | 使用 StringBuilder 或 string.Create |
热路径虚方法调用 | 频繁调用接口方法 | 通过泛型约束 where T : struct |
小对象频繁创建 | class 存储轻量数据 | 改用 readonly struct |
异步状态机分配 | 返回 Task | 返回 ValueTask |
四、性能验证工具
- 基准测试:使用
BenchmarkDotNet
量化性能差异 - 内存分析:
dotnet-counters
实时监控 GC 压力dotnet-dump
分析堆内存碎片
- CPU 优化:
- 使用
PerfView
分析热点函数 - 启用
Tiered JIT
提升启动速度
- 使用
最终建议:在高性能场景中,遵循“分配即罪恶”原则,结合数据驱动优化。优先使用内存安全特性(如 Span<T>
),仅在必要时进入 unsafe
上下文。持续通过性能剖析修正实现,避免过早优化。