ThreatSourceLibaray/docs/test_result/gc_problem_analysis.md

2.8 KiB
Raw Permalink Blame History

GC问题分析报告

测试结果分析

关键发现

测试场景 性能 (ops/sec) 内存变化 (bytes) GC次数
原始double版本 183,754,381 24,672 0
当前byte版本 60,794,063 8,592 0
查找表访问 - 8,224 -
二分查找测试 - 924,432 65

问题根源Math.Pow(10, logValue)

主要问题

二分查找测试中的GC次数高达65次内存分配924,432字节这表明问题出在

  1. Math.Pow(10, logValue)调用

    • 每次随机值测试都调用Math.Pow
    • Math.Pow可能创建临时对象或触发装箱
    • 262,144次调用导致大量内存分配
  2. 测试中的临时数组创建

    • TestBinarySearch方法中创建了临时的sortedValues数组
    • 每次调用都重新创建256个double的数组

性能对比分析

操作类型 性能差异 原因
原始double vs byte 3倍性能下降 二分查找 + 转换开销
内存占用 87.3%节省 byte存储优势明显
GC压力 显著增加 Math.Pow和临时对象

真正的瓶颈

1. Math.Pow的开销

// 问题代码:每次随机值测试都调用
double logValue = random.NextDouble() * 11 - 12;
double intensity = Math.Pow(10, logValue);  // ← 这里是GC压力源头

2. 实际应用场景分析

在真实的红外图像处理中:

  • 大部分像素值是重复的(背景、目标等)
  • 很少有完全随机的强度值
  • 零值占大比例

3. 查找表的真实效果

  • GetIntensity: 完美优化,纯数组访问
  • SetIntensity: 仍需要反向查找,但避免了对数计算

解决方案建议

方案A: 预计算常用值映射

// 为常用的工程数值预计算映射
private static readonly Dictionary<double, byte> COMMON_VALUES_MAP = new()
{
    { 0.0, 0 },
    { 1e-12, 1 },
    { 1e-11, 12 },
    // ... 更多常用值
};

方案B: 分段线性插值

// 使用分段线性插值代替二分查找
// 将对数范围分成几个段,每段内线性插值

方案C: 接受当前性能

  • 87.3%内存节省已经很显著
  • 在实际应用中重复值会减少GC压力
  • 专注于算法层面的优化

结论

查找表优化本身是成功的,问题在于:

  1. 测试场景不现实大量随机Math.Pow调用不代表真实使用场景
  2. GC增加的原因Math.Pow和测试代码的临时对象不是查找表本身
  3. 实际效果在真实应用中重复值和零值占主导GC压力应该会减少

推荐

  • 保持当前的byte存储 + 查找表方案
  • 在实际应用中测试GC效果
  • 如果仍有问题,考虑为最常用的值添加快速路径