生成不同点列表但运行OutOfMemory

时间:2013-07-11 01:20:27

标签: c# dictionary out-of-memory

我正在努力提高我们所拥有的方法的性能,该方法将点从一个坐标系重新投影到另一个坐标系。

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;

当我执行超过大约8M点时,我当前遇到的问题是OutOfMemoryException。我想知道是否有更好的方法来做到这一点?

2 个答案:

答案 0 :(得分:1)

此解决方案可能会减少整体内存占用,同时保持顺序并仅转换唯一的点:

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

答案 1 :(得分:1)

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设置合适的初始大小(确实需要为字典,以便快速查找)。理想大小是素数,它比points.Count略大。 (我没有找到建议多大的参考 - 可能是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可能仅仅将对象从第1代推送到第2代,在完成GC的情况下,它们将不会被收集。我只在用户请求完成操作需要5秒以上的操作时调用它,并且已经证明它容易出现OOM。