我正在努力提高我们所拥有的方法的性能,该方法将点从一个坐标系重新投影到另一个坐标系。
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。我想知道是否有更好的方法来做到这一点?
答案 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。