2

我正在使用 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)

我已经对这两种方法进行了概要分析,“之前”版本的速度相当快,大约 18% 的时间用于 GC,而“之后”版本的时间大约为 88%。很明显,添加此缓存使 GC 活动增加,但我看不出这是怎么可能的。

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

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

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

编辑#2:after方法的四个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

4 回答 4

2

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

分配触发收集,因此分配越多,收集越频繁。

如果应用程序保存大量数据,那么 Gen 2 集合可能会变得非常昂贵。如果您看到在 GC 中花费了很多时间,通常就是这种情况(收集 Gen 0 和 1 很快,因为它们的大小有限)。

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

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

于 2013-06-18T16:16:30.003 回答
1

缓存可能会给您的应用程序带来额外的内存开销,这反过来又会导致应用程序运行时可用于其他目的的可用内存量较少。在这种情况下,垃圾收集器不仅被迫更频繁地运行,而且随着内存负载占应用程序可用总内存的百分比增加,效率通常也会降低。

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

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

于 2013-06-16T23:58:35.873 回答
1
  1. 使用 Parallel.For 可能会产生堆开销(尝试使用更大的块大小)。
  2. ModelEstimate 可能有堆开销。使用适合您框架的版本运行CLRProfiler 。它会告诉你你正在分配什么以及堆中发生了什么。

环境变量:

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

于 2013-06-16T16:43:35.137 回答
0

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

微软在“垃圾收集指南”中发现了一个非常有趣的建议,其中指出

在处理缓存数据时考虑使用弱引用,这样缓存对象可以在需要时轻松复活,或者在内存压力时通过垃圾回收释放。

例子

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
}
于 2016-02-27T19:51:55.453 回答