正在生成不同点的列表,但正在运行OutOfMemory
本文关键字:OutOfMemory 运行 不同点 列表 | 更新日期: 2023-09-27 18:21:50
我正在努力提高一种方法的性能,该方法将点从一个坐标系重新投影到另一个坐标系统。
List<Point> Reproject(List<Point> points, string sourceProjection, string destinationProjection)
为了进行坐标转换,我们将点传递到第三方库(FME)中。我目前试图实现的是获取点的输入列表,只从该列表中选择不同的点,只将这些点传递到转换引擎中,然后重建原始列表并将其返回给客户端。
我的基本计划是使用Dictionary<Point, int>
来获取所有不同的点,并为它们分配索引,然后用索引重建原始列表。以下是我为测试这种行为而编写的一些粗略代码:
var distinctPoints = new Dictionary<Point, int>();
var distinctPointsMapping = new Dictionary<int, int>();
var pointNumber = 0;
var distinctPointNumber = 0;
foreach (var point in points)
{
if (distinctPoints.ContainsKey(point))
{
distinctPointsMapping.Add(pointNumber, distinctPoints[point]);
}
else
{
distinctPoints.Add(point, distinctPointNumber);
distinctPointsMapping.Add(pointNumber, distinctPointNumber);
distinctPointNumber++;
}
pointNumber++;
}
Console.WriteLine("From an input of {0} points, I found {1} distinct points.", points.Count, distinctPointNumber);
var transformedPoints = new Point[distinctPointNumber]; // replace this with the call to the FME transformer
var returnVal = new List<Point>(points.Count);
pointNumber = 0;
foreach (var untransformedPoint in points)
{
var transformedPoint = transformedPoints[distinctPointsMapping[pointNumber]];
returnVal.Add(transformedPoint);
pointNumber++;
}
return returnVal;
我目前遇到的问题是,当我获得超过800万分时,会出现OutOfMemoryException。我想知道是否有更好的方法?
1。字典占用大量内存。自动调整大型字典的大小特别容易出现问题(内存碎片=>OOM早在您预期之前)。
替换:
var distinctPointsMapping = new Dictionary<int, int>();
...
distinctPointsMapping.Add(pointNumber, distinctPoints[point]);
...
distinctPointsMapping.Add(pointNumber, distinctPointNumber);
使用:
var distinctPointsMapping = new List<Int>(points.Count);
...
distinctPointsMapping[pointNumber] = distinctPoints[point];
...
distinctPointsMapping[pointNumber] = distinctPointNumber;
2.要减少内存碎片,请考虑为distinctPoints设置适当的初始大小(它确实需要是一个字典,以便快速查找)。理想的大小是一个素数,它比点大一些。计数(我找不到一个参考资料来说明要大多少——可能是25%?)。
// You have to write "CalcDictionarySize". See above text.
int goodSize = CalcDictionarySize(points.Count);
var distinctPoints = new Dictionary<Point, int>(goodSize);
3.在极端情况下,请在运行代码之前请求GC。(这个建议可能有争议。然而,当我无法找到任何其他方法来避免OOM时,我自己也成功地使用了它。)
public void GarbageCollect_Major()
{
// Force GC of two generations - to get any recent unneeded objects up to their finalizers.
GC.Collect(1, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
// This may be dubious. But it seemed to maintain more responsive system.
// (perhaps 5-20 ms) Because full GC stalls .Net, give time to threads (related to GUI?)
System.Threading.Thread.Sleep(10);
}
然后在你的方法开始时:
GarbageCollect_Major();
CAVEAT:显式调用GC不是一件容易的事情。也不经常。"过于频繁"的GC可能只是将对象从Gen 1推送到Gen 2,在那里它们不会被收集,直到完成完整的GC。只有当用户请求一个需要5秒以上才能完成的操作时,我才会调用它,而且这已经被证明是容易OOM的。
此解决方案可能会减少总体内存占用,同时保持秩序并仅转换唯一点:
List<Point> Reproject(List<Point> points, string sourceProjection, string destinationProjection)
{
List<Point> returnPoints = new List<Point>(points.Count);
var transformedPoints = new Dictionary<Point, Point>();
foreach(var point in points)
{
Point projectedPoint;
if (!transformedPoints.TryGetValue(point, out projectedPoint))
{
projectedPoint = FMETransform(point, sourceProjection, destinationProjection);
transformedPoints.Add(point, projectedPoint);
}
returnPoints.Add(projectedPoint);
}
return returnPoints;
}
但总的来说,可能仍然是一块很好的内存。如果可能的话,也许您可以牺牲性能(转换所有点,甚至是重复点)来减少内存使用。再加上一些延迟处理,也许你只能根据需要转换点,然后停止迭代,或者至少让垃圾收集器在一些旧点未使用时拾取它们:
private IEnumerable<Point> Reproject(IEnumerable<Point> points, string sourceProjection, string destinationProjection)
{
foreach(Point p in points)
yield return FMETransform(p, sourceProjection, destinationProjection);
}