简单的缓存让GC变得疯狂?

时间:2013-06-15 09:17:04

标签: c# .net garbage-collection

我使用C#3.5和VS 2010终极版。

我优化(速度)机器学习算法,它有四个嵌套的for循环。 我发现一个简单的缓存(一个对象的张量)可能会大大提高性能,因为有很多重新分配相同的对象。

这是实施之前和之后。

在:

four nested for-loops:
   var object = new object(3 parameters);
   Calculate(object, other params)

后:

var cache = new object[count1,count2,count3];
three nested for-loops:
   cache[param1, param2, param3] = new object(3 params);

four nested for-loops:
   var object = cache[3 parameters];
   Calculate(object, other params)

我已经描述了这两种方法和"之前"版本速度相当快,在GC中花费了大约18%的时间,而在"之后#34;版本在GC中花费了大约88%。 显而易见,添加此缓存会使GC活动增加,但我没有看到这是如何实现的。

我在应用程序中使用了许多长期存在的对象。在分析时我的机器没有处于重负荷状态。张量是使用多维数组(不是锯齿状数组)实现的。上面两种方法中最内层的循环是使用Parallel.For构造实现的,在循环之前,我正在分配一个小的double数组。

如何减少在GC中花费的时间?

编辑#1:结果确实来自发布模式。

编辑#2:后方法的实际代码是四个for循环:

List<int> labels = // count = 80
List<int> tokens = // count = 35

var table = new double[tokens.Count, labels.Count, labels.Count];
var cachedObjects = new CachedObject[tokens.Count, labels.Count, labels.Count];
for (int k = 0; k < tokens.Count; k++)
{
    foreach (var tagCurrent in labels) 
    {
        foreach (var labelBack in labels)
        {
            double[] value = new double[labels.Count];
            Parallel.For(0, labels.Count, (i) => 
            {
                CachedObject CachedObject = cachedObjects[k, labelsBackFurther[i], labelBack];

                var me = ModelEstimate(vOptimal, CachedObject, tagCurrent, labels);
                value[i] = table[k - 1, labels[i], labelBack] * me;
            }); 

            var maxValue = 0;
            var maxTagIdx = 0;
            for (int j = 0; j < value.Length; j++)
            {
                var item = value[j];
                if (item > maxValue)
                {
                    maxValue = item;
                    maxTagIdx = j;
                }
            }

            table[k, labelBack, tagCurrent] = maxValue;
        }
    }
}

4 个答案:

答案 0 :(得分:2)

GC受两个因素的影响:分配数量和幸存者数量。

分配触发器会收集,因此您分配的频率越高。

如果应用程序持有大量数据,则第2代收藏可能会变得非常昂贵。如果您在GC中花费了很多时间,通常就是这种情况(收集第0代和第1代的速度很快,因为它们的大小有限)。

在你的情况下,听起来你想要保留缓存。如果你这样做,你需要确保你保持分配,因为你不想触发昂贵的Gen 2收集。

您可以使用PerfView来跟踪分配。

答案 1 :(得分:1)

  1. 使用Parallel.For可能会产生堆开销(尝试使用更大的块大小)。
  2. ModelEstimate可能有堆开销。 使用适用于您的框架的版本运行CLRProfiler。它会告诉您分配的内容以及堆中发生的情况。
  3. 环境变量:

      

    OMV_PATH = C:\ WINDOWS \ Temp表示放置日志文件的位置。

答案 2 :(得分:1)

缓存可能会在应用程序上增加额外的内存开销,从而导致应用程序以较少的可用内存运行以用于其他目的。在这种情况下,垃圾收集器不仅被迫更频繁地运行,而且由于内存负载增长占应用程序可用总内存的百分比,因此效率通常较低。

如果您有足够的系统内存,请尝试将应用程序重新运行为64位应用程序,以查看问题是否仍然存在。

根据一次使用的缓存元素的数量,可能会导致不必要地阻止多达count1*count2*count3 - 1个对象以及多维数组本身被收集。

答案 3 :(得分:0)

如何减少在GC中花费的时间?

&#34; Garbage Collection Guidelines&#34;中有一个非常有趣的建议。微软表示

  

在使用缓存数据时,请考虑使用弱引用   如果需要或释放,缓存的对象可以很容易地复活   有内存压力的垃圾收集。

实施例

void SomeMethod() 
{
  // Create a collection
  var arr = new ArrayList(5);
  // Create a custom object
  var mo = new MyObject();
  // Create a WeakReference object from the custom object
  var wk = new WeakReference(mo);
  // Add the WeakReference object to the collection
  arr.Add(wk);
  // Retrieve the weak reference
  WeakReference weakReference = (WeakReference)arr[0];
  MyObject mob = null;
  if( weakReference.IsAlive ){
    mob = (MyOBject)weakReference.Target;
  }
  if(mob==null){
    // Resurrect the object as it has been garbage collected
  }
  //continue because we have the object
}