正在生成不同点的列表,但正在运行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。我想知道是否有更好的方法?

正在生成不同点的列表,但正在运行OutOfMemory

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);
}