156 lines
4.7 KiB
Markdown
156 lines
4.7 KiB
Markdown
# 威胁源仿真库对象池优化分析报告
|
||
|
||
## 优化尝试总结
|
||
|
||
### 背景
|
||
在发现`InfraredTargetRecognizer.cs`中存在LOH分配问题后,我们实施了对象池优化,试图通过池化大型数组来减少GC压力。
|
||
|
||
### 优化目标
|
||
- 消除640x480像素图像处理中的LOH分配(约300KB/次)
|
||
- 减少Gen1/Gen2 GC频率
|
||
- 提升整体性能
|
||
|
||
## 实施的优化方案
|
||
|
||
### 1. 创建ImageProcessingPool
|
||
```csharp
|
||
// 实现了简化版对象池
|
||
public class ImageProcessingPool
|
||
{
|
||
private readonly ConcurrentBag<bool[,]> _visitedArrays = new();
|
||
private readonly ConcurrentBag<double[,]> _smokeIntensityArrays = new();
|
||
|
||
public bool[,] GetVisitedArray(int width, int height)
|
||
{
|
||
if (!_visitedArrays.TryTake(out var array))
|
||
{
|
||
array = new bool[_maxHeight, _maxWidth]; // 仍然LOH分配
|
||
}
|
||
return array;
|
||
}
|
||
|
||
public void ReturnVisitedArray(bool[,] array)
|
||
{
|
||
Array.Clear(array, 0, array.Length); // CPU密集操作
|
||
_visitedArrays.Add(array);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 修改图像处理代码
|
||
- `InfraredTargetRecognizer.SegmentTarget()` - 使用池化的visited数组
|
||
- `InfraredImageGenerator.GenerateImage()` - 使用池化的烟幕数组
|
||
|
||
## 性能测试结果对比
|
||
|
||
### 测试配置
|
||
- **场景**: 50个目标, 20个导弹, 30秒测试
|
||
- **帧率**: 50fps
|
||
- **模式**: Release
|
||
|
||
### 关键指标对比
|
||
|
||
| 指标 | 优化前 | 优化后 | 变化率 |
|
||
|------|--------|--------|-------|
|
||
| 平均帧时间 | 0.36ms | 2.97ms | +725% ❌ |
|
||
| 最大帧时间 | 20.57ms | 47.74ms | +132% ❌ |
|
||
| Gen0 GC频率 | 1.3次/秒 | 9.2次/秒 | +607% ❌ |
|
||
| Gen1 GC频率 | 1.1次/秒 | 8.5次/秒 | +673% ❌ |
|
||
| Gen2 GC频率 | 1.1次/秒 | 8.5次/秒 | +673% ❌ |
|
||
| 内存增长率 | 0.22MB/s | 0.46MB/s | +109% ❌ |
|
||
|
||
## 失败原因分析
|
||
|
||
### 1. **Array.Clear性能开销**
|
||
```csharp
|
||
// 对300KB数组的清理操作
|
||
Array.Clear(array, 0, 640 * 480); // 307,200次内存写入
|
||
```
|
||
**影响**: 每次归还数组都要进行大量内存写入,CPU开销巨大
|
||
|
||
### 2. **ConcurrentBag同步开销**
|
||
```csharp
|
||
// 线程安全集合的性能代价
|
||
_visitedArrays.TryTake(out var array); // 同步操作
|
||
_visitedArrays.Add(array); // 同步操作
|
||
```
|
||
**影响**: 频繁的线程同步操作增加了额外开销
|
||
|
||
### 3. **对象池容量不足**
|
||
- 当池为空时,仍然创建新的LOH对象
|
||
- 没有预热机制,启动时池为空
|
||
- 没有合适的容量规划
|
||
|
||
### 4. **过度设计**
|
||
- 简单的内存分配问题被复杂的对象池方案掩盖
|
||
- 引入了新的性能瓶颈
|
||
- 代码复杂性大幅增加
|
||
|
||
## 根本原因探索
|
||
|
||
### 原始性能好的真实原因
|
||
回顾最初的优秀性能测试结果(0.36ms帧时间),可能的原因:
|
||
|
||
1. **Debug.WriteLine优化**: Release模式下完全消除
|
||
2. **LINQ优化**: 减少了临时集合分配
|
||
3. **测试场景差异**: 可能图像处理调用频率较低
|
||
|
||
### LOH分配的实际影响
|
||
虽然640x480数组确实会进入LOH,但:
|
||
- **分配频率**: 可能并不像预期那样频繁
|
||
- **GC策略**: .NET的LOH管理已经相当优化
|
||
- **实际成本**: 对象池的管理成本可能超过了LOH分配成本
|
||
|
||
## 经验教训
|
||
|
||
### 1. **过早优化的代价**
|
||
- 在没有准确性能剖析的情况下就进行复杂优化
|
||
- 假设LOH分配是主要瓶颈,但可能不是
|
||
|
||
### 2. **对象池的适用场景**
|
||
对象池适合:
|
||
- 高频分配的小到中等对象
|
||
- 创建成本高的对象
|
||
- 确定的容量需求
|
||
|
||
对象池不适合:
|
||
- 超大对象(如300KB数组)
|
||
- 低频使用的对象
|
||
- 需要频繁清理的对象
|
||
|
||
### 3. **性能优化的正确方法**
|
||
1. **先测量,后优化**
|
||
2. **一次只改变一个变量**
|
||
3. **用实际workload进行测试**
|
||
4. **考虑优化的总成本**
|
||
|
||
## 下一步建议
|
||
|
||
### 立即行动
|
||
1. **回滚对象池优化**,恢复到之前的良好性能
|
||
2. **保留其他有效优化**(如LINQ消除、Debug.WriteLine处理)
|
||
|
||
### 深入分析
|
||
1. **使用专业profiler**(如JetBrains dotMemory, PerfView)
|
||
2. **分析真实的内存分配热点**
|
||
3. **测量LOH分配的实际频率和影响**
|
||
|
||
### 替代方案
|
||
1. **减少图像处理频率**(如每2-3帧处理一次)
|
||
2. **降低图像分辨率**(如320x240)
|
||
3. **使用stackalloc for小数组**
|
||
4. **实现增量处理算法**
|
||
|
||
## 总结
|
||
|
||
这次对象池优化尝试虽然失败了,但提供了宝贵的经验:
|
||
|
||
1. **复杂的优化不一定更好**
|
||
2. **测量比假设更重要**
|
||
3. **.NET的内存管理已经相当优化**
|
||
4. **过度工程化可能适得其反**
|
||
|
||
最初的优秀性能(0.36ms帧时间)表明C#仿真库已经具备了良好的性能基础。我们应该专注于:
|
||
- 保持现有的良好性能
|
||
- 通过精确测量找到真正的瓶颈
|
||
- 采用简单而有效的优化策略 |