C# 简单缓存让GC变得疯狂?
我使用的是C#3.5和VS 2010 ultimate 我正在优化(速度)一个机器学习算法,它有四个嵌套for循环。 我发现一个简单的缓存(对象的张量)可以极大地提高性能,因为有很多相同对象的重新分配 以下是实施前后的情况 之前:C# 简单缓存让GC变得疯狂?,c#,.net,garbage-collection,C#,.net,Garbage Collection,我使用的是C#3.5和VS 2010 ultimate 我正在优化(速度)一个机器学习算法,它有四个嵌套for循环。 我发现一个简单的缓存(对象的张量)可以极大地提高性能,因为有很多相同对象的重新分配 以下是实施前后的情况 之前: four nested for-loops: var object = new object(3 parameters); Calculate(object, other params) 之后: var cache = new object[count1
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%的时间,“之后”版本在GC中花费了约88%的时间。
很明显,这个缓存的添加使GC活动增加了,但是我没有看到这是怎么可能的
我在应用程序中使用了许多长寿命对象。在分析时,我的机器没有承受重负载。张量是使用多维数组(不是锯齿数组)实现的。上述两种方法中最内部的循环都是使用并行实现的。对于
构造,在循环的前面我分配了一个小的双
数组
如何减少在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;
}
}
}
列表标签=//计数=80
列表标记=//计数=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
{
CachedObject CachedObject=cachedObjects[k,labelbackfurther[i],labelBack];
var me=模型估计(VoOptimal、CachedObject、tagCurrent、labels);
值[i]=表[k-1,标签[i],标签背]*me;
});
var maxValue=0;
var maxTagIdx=0;
对于(int j=0;j最大值)
{
最大值=项目;
maxTagIdx=j;
}
}
表[k,labelBack,tagCurrent]=最大值;
}
}
}
缓存可能会给应用程序带来额外的内存开销,这反过来会导致应用程序在运行时使用较少的可用内存用于其他目的。在这种情况下,垃圾收集器不仅被迫更频繁地运行,而且随着内存负载占应用程序可用内存的百分比的增加,其效率往往会降低 如果您有足够的系统内存,请尝试将应用程序作为64位应用程序重新运行,以查看问题是否仍然存在
根据一次使用的缓存元素的数量,可能会导致不必要地阻止收集多达
count1*count2*count3-1的对象以及多维数组本身。缓存可能会给应用程序带来额外的内存开销,这反过来又会导致应用程序在运行时使用较少的可用内存用于其他目的。在这种情况下,垃圾收集器不仅被迫运行得更频繁,而且随着内存负载占应用程序可用内存的百分比的增加,效率通常会降低
如果您有足够的系统内存,请尝试将应用程序作为64位应用程序重新运行,以查看问题是否仍然存在
根据一次使用的缓存元素的数量,您可能会导致不必要地阻止收集多达count1*count2*count3-1的对象以及多维数组本身。GC受两个因素的影响:分配数量和幸存者数量
分配触发器收集,因此分配越多,收集越频繁
如果应用程序保留了大量数据,那么第2代收集可能会变得非常昂贵。如果您看到在GC中花费了很多时间,通常情况就是这样(收集Gen 0和Gen 1很快,因为它们的大小有限)
在您的情况下,听起来您希望保留缓存。如果你这样做,你需要确保你保持低分配,因为你不想触发昂贵的第2代收集
您可以使用跟踪分配。GC受两个因素影响:分配数量和幸存者数量
分配触发器收集,因此分配越多,收集越频繁
如果应用程序保留了大量数据,那么第2代收集可能会变得非常昂贵。如果您看到在GC中花费了很多时间,通常情况就是这样(收集Gen 0和Gen 1很快,因为它们的大小有限)
在您的情况下,听起来您希望保留缓存。如果你这样做,你需要确保你保持低分配,因为你不想触发昂贵的第2代收集
您可以使用来跟踪分配。如何减少在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
}