ThreatSourceLibaray/docs/test_result/performance_optimization_analysis.md

4.7 KiB
Raw Blame History

威胁源仿真库对象池优化分析报告

优化尝试总结

背景

在发现InfraredTargetRecognizer.cs中存在LOH分配问题后我们实施了对象池优化试图通过池化大型数组来减少GC压力。

优化目标

  • 消除640x480像素图像处理中的LOH分配约300KB/次)
  • 减少Gen1/Gen2 GC频率
  • 提升整体性能

实施的优化方案

1. 创建ImageProcessingPool

// 实现了简化版对象池
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性能开销

// 对300KB数组的清理操作
Array.Clear(array, 0, 640 * 480); // 307,200次内存写入

影响: 每次归还数组都要进行大量内存写入CPU开销巨大

2. ConcurrentBag同步开销

// 线程安全集合的性能代价
_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#仿真库已经具备了良好的性能基础。我们应该专注于:

  • 保持现有的良好性能
  • 通过精确测量找到真正的瓶颈
  • 采用简单而有效的优化策略