1

我正在尝试提高一种方法的性能,该方法将点从一个坐标系重新投影到另一个坐标系。

List<Point> Reproject(List<Point> points, string sourceProjection, string destinationProjection)

为了进行坐标转换,我们将点传递到第 3 方库 (FME)。我目前想要实现的是获取点的输入列表,仅从该列表中选择不同的点,仅将这些点传递到转换引擎,然后重建原始列表并将其返回给客户端。

我的基本计划是使用 aDictionary<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。我想知道是否有更好的方法来做到这一点?

4

2 回答 2

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);
}
于 2013-07-11T03:28:15.030 回答
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 可能只是将对象从 Gen 1 推到 Gen 2,在 FULL GC 完成之前,它们不会被收集。我只在用户请求一个需要超过 5 秒才能完成的操作时才调用它,并且这已被证明容易出现 OOM。

于 2013-07-11T07:44:10.527 回答