我有一个非常糟糕的效率问题。 我需要在一组点和我的第一个"蛮力"之间达到最远的距离。算法需要将近80秒。我需要它在1秒内发生。 最糟糕的情况是将计算转移到后台进程并对它们进行多线程处理,但它仍然需要更快,所以这是我的第一个stackoverflow问题。
我拥有的数据是39 000组坐标,每组包含大约200 x,y坐标,我在每组中寻找最远距离。
数据点由x和y表示,我用Math.Sqrt(deltaX * deltaX + deltaY * deltaY)
计算它们之间的距离
数据点可以按任何顺序排列。
我的暴力尝试看起来像这样
public static double getAbsoluteMax(IEnumerable<DataPoint> dataPoints)
{
double maxDistance = 0;
foreach (DataPoint dp1 in dataPoints)
{
foreach (DataPoint dp2 in dataPoints)
{
double deltaX = dp1.x - dp2.x;
double deltaY = dp1.y - dp2.y;
double distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > maxDistance)
{
maxDistance = distance;
}
}
}
return maxDistance;
}
我每次使用200个值调用此函数.39 000次。
我的第一个想法是在Perl中找到的Memoize,它缓存任何方法调用的结果,然后查找是否使用相同的参数调用相同的方法。也许创建一个包含数学结果的查找表可能有帮助吗?
也许我可以将计算转移到matlab或类似的东西?
应用程序是.net 4.5,计算是在.net 4.5 dll
答案 0 :(得分:3)
在n log n中找到凸包。它可能会减少你的设置,然后在2 n内找到船体直径。基本图论应该会给你很多表现。
此外,函数调用开销39000次是昂贵的......数据集也会杀死垃圾收集器。你应该尝试创建一些可重用的数组...... 78000个枚举器正在查杀。只需使用200个值的数组并重用它。而不是为每个使用int ...删除sqrt并返回值平方它节省了几百万平方......也许你可以使用int只有没有双打...使用多个线程/核心...并且sse指令或卸载到了gpu
在
上编译发布版本和代码优化例如,它在大约2.5秒内运行:
Random r = new Random(100);
double[] x = new double[200];
double[] y = new double[200];
double maxD = 0;
Stopwatch stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 39000; i++)
{
for (int j = 0; j < 200; j++)
{
x[j] = r.Next();
y[j] = r.Next();
}
for (int j = 0; j < 200; j++)
{
for (int k = j + 1; k < 200; k++)
{
double dx = x[j] - x[k];
dx = dx * dx;
double dy = y[j] - y[k];
dy = dy * dy;
double d = dx + dy;
// this is slow (80 secs):
//double d = Math.Pow(x[j] - x[k], 2) + Math.Pow(y[j] - y[k], 2);
if (maxD < d) maxD = d;
}
}
}
Console.WriteLine($"{stopwatch.ElapsedMilliseconds}");
Math.Pow调用(来自http://referencesource.microsoft.com/#mscorlib/system/math.cs):
[System.Security.SecuritySafeCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static extern double Pow(double x, double y);
答案 1 :(得分:1)
对于任何点集:如果两个点a和b的距离大于或等于所有其他点对,则它们是凸包的一部分。
知道这一点,你可以先计算凸包,然后用你的方法只计算凸包上的点。
Matlab中的示例实现
示例数据:
points=rand(2000,2);
使用pdist的简单解决方案
distance_matrix=squareform(pdist(points));
[distance,index]=max(distance_matrix(:));
[a,b]=ind2sub(size(distance_matrix),index);
fprintf('the points with index %d and %d have the largest distance\n',a,b)
&#34;智能&#34;使用凸包的方式
c=convhull(points(:,1),points(:,2));
%don't need the duplicated last/first entry which loops
c=c(1:end-1);
distance_matrix=squareform(pdist(points(c,:)));
[distance,index]=max(distance_matrix(:));
[a,b]=ind2sub(size(distance_matrix),index);
fprintf('the points with index %d and %d have the largest distance\n',c(a),c(b))
对于200个随机点,两种解决方案的运行时间均小于0.01秒。
答案 2 :(得分:0)
public static double GetAbsoluteMax(List<DataPoint> list) {
double max = 0;
// calc dist for each distinct pair Dist(P_1, P_2) == Dist(P_2, P_1)
for(var i=0; i<list.Count-1; i++) {
for(var j=i+1; j<list.Count; j++) {
var dX = list[i].X - list[j].X;
var dY = list[i].Y - list[j].Y;
// don't calculate the Square Root yet
var dist = dX * dX + dY * dY;
if(dist > max) {
max = dist;
}
}
}
return Math.Sqrt(max);
}
ideone http://ideone.com/KSfm0X
你能做的最好的不是重复距离计算,也不计算直到最后的平方根。
编辑这里是一个额外的多线程/并行版本(我使用的是ThreadPool,所以它可以在IDEONE.COM中运行,但如果你的目标是使用任务可能会更合适.NET 4.5
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
public class Test
{
public class DataPoint {
public double X {get; set;}
public double Y {get; set;}
}
public static void Main()
{
int count = 39;
// generate our points
List<DataPoint>[] sets = new List<DataPoint>[count];
double[] result = new double[count];
for(var i=0; i<sets.Length; i++) {
sets[i] = GetRandomDataPoints(200);
}
// run our calculations async
int running = count;
using(ManualResetEvent resetEvent = new ManualResetEvent(false)){
for(int i=0; i<count; i++) {
int idx = i;
ThreadPool.QueueUserWorkItem(
new WaitCallback(o => {
result[idx] = GetAbsoluteMax(sets[idx]);
if (Interlocked.Decrement(ref running) == 0) {
resetEvent.Set();
}
}),
null
);
}
resetEvent.WaitOne();
}
foreach(var max in result) {
Console.WriteLine(max);
}
}
public static double GetAbsoluteMax(List<DataPoint> list) {
double max = 0;
// calc dist for each distinct pair Dist(P_1, P_2) == Dist(P_2, P_1)
for(var i=0; i<list.Count-1; i++) {
for(var j=i+1; j<list.Count; j++) {
var dX = list[i].X - list[j].X;
var dY = list[i].Y - list[j].Y;
// don't calculate the Square Root yet
var dist = dX * dX + dY * dY;
if(dist > max) {
max = dist;
}
}
}
return Math.Sqrt(max);
}
public static List<DataPoint> GetRandomDataPoints(int size) {
var result = new List<DataPoint>();
var rnd = new Random();
for(var i=0; i<size; i++) {
result.Add(new DataPoint() {
X=rnd.NextDouble()*100,
Y=rnd.NextDouble()*100
});
}
return result;
}
}
答案 3 :(得分:-1)
以下是您如何确保两次不比较数据点,并且只迭代IEnumerable
一次。它还会保存Math.Sqrt
以结束。
public static double getAbsoluteMax(IEnumerable<DataPoint> dataPoints)
{
double maxDistanceSquared = 0;
var previousPoints = new List<DataPoint> { dataPoints.FirstOrDefault() };
foreach (DataPoint dp1 in dataPoints.Skip(1))
{
foreach (DataPoint dp2 in previousPoints)
{
double deltaX = dp1.x - dp2.x;
double deltaY = dp1.y - dp2.y;
double distanceSquared = (deltaX * deltaX) + (deltaY * deltaY);
if (distanceSquared > maxDistanceSquared)
{
maxDistanceSquared = distanceSquared;
}
}
previousPoints.Add(dp1);
}
return Math.Sqrt(maxDistanceSquared);
}
基本上它的作用是将第一个点放入一个列表然后迭代其余的点。对于每个点,它然后迭代列表并进行距离检查。然后它将该点添加到列表中。因此,每次迭代只会将该点与之前的点进行比较。
答案 4 :(得分:-1)
它仍然是蛮力,但我把它从12秒降到了6:
1)将列表预处理为单独的x&amp; y数组
2)缓存外循环中的点
public static double GetAbsoluteMax(double[] xs, double[] ys)
{
double maxDistanceSqd = 0;
int nPts = xs.Length;
for ( int i =0; i < nPts ; ++i )
{
double x1 = xs[i];
double y1 = ys[i];
for (int j = 0; j < nPts; ++j)
{
double deltaX = x1 - xs[j];
double deltaY = y1 - ys[j];
double distsqd = (deltaX * deltaX + deltaY * deltaY);
if (distsqd > maxDistanceSqd)
{
maxDistanceSqd = distsqd;
}
}
}
return Math.Sqrt(maxDistanceSqd);
}