Не зная ничего больше о диапазоне и точности значений или слишком много думая о распределении поисков по сравнению с изменениями списка контрольных точек, некоторые простые оптимизации цикла for
дают примерно 30-кратное ускорение для 100 поисков по сравнению сбыстрее OrderBy
/ First
код:
Используя pld
для ваших ProcessedLineData
и data
для Modifications.Value_and_Ref_Calc.ReferenceFile.Dataset[0].Data
вы получите:
var _closestSample = data[0];
var dist = (_closestSample.Easting - pld.Easting) * (_closestSample.Easting - pld.Easting) + (_closestSample.Northing - pld.Northing) * (_closestSample.Northing - pld.Northing);
for (int j2 = 1; j2 < data.Count; ++j2) {
var y = data[j2];
var ydist = (y.Easting - pld.Easting) * (y.Easting - pld.Easting) + (y.Northing - pld.Northing) * (y.Northing - pld.Northing);
if (ydist < dist) {
dist = ydist;
_closestSample = y;
}
}
С моим временем более2 000 000 записей data
списка и 100 поисков, OrderBy
/ First
занимает 2,22 секунды, а for
занимает 0,06 секунды для ускорения в 32 раза.
Итак, я был уверен, что есть лучший способчем грубой силой, и после некоторых исследований обнаружили коды Мортона и кривые Гильберта . Небольшая работа сгенерировала класс SpatialIndex
с использованием кривой Гильберта и класс SpatialIndexMorton
с использованием индекса Мортона. Я также настроил индекс Гильберта, чтобы индексировать только биты 16-32, что обеспечивало оптимальный поиск в секунду. По моим данным, кривая Мортона теперь несколько быстрее.
Используя те же тесты случайных данных, я получаю, что метод for
может выполнять 147 просмотров в секунду, поиск по Гильберту с индексом 5634 в секунду и МортонаИндексируйте 7370 поисков в секунду более 10 000 поисков с 2 000 000 контрольных точек. Обратите внимание, что пространственные индексы имеют время установки около 3 секунд, поэтому для очень небольшого числа поисков быстрее использовать грубую силу с for
- я получаю время безубыточности около 468 поисков.
Чтобы сделать это (несколько) общего назначения, я начал с (C # 8.0) интерфейса для координат Земли, который предоставляет несколько вспомогательных методов:
public interface ICoordinate {
double Longitude { get; set; }
double Latitude { get; set; }
public ulong MortonCode() {
float f = (float)Latitude;
uint ui;
unsafe { // perform unsafe cast (preserving raw binary)
float* fRef = &f;
ui = *((uint*)fRef);
}
ulong ixl = ui;
f = (float)Longitude;
unsafe { // perform unsafe cast (preserving raw binary)
float* fRef = &f;
ui = *((uint*)fRef);
}
ulong iyl = ui;
ixl = (ixl | (ixl << 16)) & 0x0000ffff0000ffffL;
iyl = (iyl | (iyl << 16)) & 0x0000ffff0000ffffL;
ixl = (ixl | (ixl << 8)) & 0x00ff00ff00ff00ffL;
iyl = (iyl | (iyl << 8)) & 0x00ff00ff00ff00ffL;
ixl = (ixl | (ixl << 4)) & 0x0f0f0f0f0f0f0f0fL;
iyl = (iyl | (iyl << 4)) & 0x0f0f0f0f0f0f0f0fL;
ixl = (ixl | (ixl << 2)) & 0x3333333333333333L;
iyl = (iyl | (iyl << 2)) & 0x3333333333333333L;
ixl = (ixl | (ixl << 1)) & 0x5555555555555555L;
iyl = (iyl | (iyl << 1)) & 0x5555555555555555L;
return ixl | (iyl << 1);
}
const int StartBitMinus1 = 31;
const int EndBit = 16;
//convert (x,y) to 31-bit Hilbert Index
public ulong HilbertIndex() {
float f = (float)Latitude;
uint x;
unsafe { // perform unsafe cast (preserving raw binary)
float* fRef = &f;
x = *((uint*)fRef);
}
f = (float)Longitude;
uint y;
unsafe { // perform unsafe cast (preserving raw binary)
float* fRef = &f;
y = *((uint*)fRef);
}
ulong hi = 0;
for (int bitpos = StartBitMinus1; bitpos >= EndBit; --bitpos) {
// extract s'th bit from x & y
var rx = (x >> bitpos) & 1;
var ry = (y >> bitpos) & 1;
hi <<= 2;
hi += (rx << 1) + (rx ^ ry);
//rotate/flip a quadrant appropriately
if (ry == 0) {
if (rx == 1) {
x = ~x;
y = ~y;
}
//Swap x and y
uint t = x;
x = y;
y = t;
}
}
return hi;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double DistanceTo(ICoordinate b) =>
Math.Sqrt((Longitude - b.Longitude) * (Longitude - b.Longitude) + (Latitude - b.Latitude) * (Latitude - b.Latitude));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double Distance2To(ICoordinate b) => (Longitude - b.Longitude) * (Longitude - b.Longitude) + (Latitude - b.Latitude) * (Latitude - b.Latitude);
public ICoordinate MakeNew(double plat, double plong);
}
public static class ICoordinateExt {
public static ICoordinate Minus(this ICoordinate a, ICoordinate b) =>
a.MakeNew(a.Latitude - b.Latitude, a.Longitude - b.Longitude);
public static ICoordinate Plus(this ICoordinate a, ICoordinate b) =>
a.MakeNew(a.Latitude + b.Latitude, a.Longitude + b.Longitude);
}
Затем реальный класс дляреализовать интерфейс (который будет заменен вашим реальным классом):
public class PointOfInterest : ICoordinate {
public double Longitude { get; set; }
public double Latitude { get; set; }
public PointOfInterest(double plat, double plong) {
Latitude = plat;
Longitude = plong;
}
public ICoordinate MakeNew(double plat, double plong) => new PointOfInterest(plat, plong);
}
И класс для преобразования IEnumerable<ICoordinate>
в пространственно индексированную коллекцию ICoordinate
с использованием кривой Гильберта:
public class SpatialIndex {
SortedList<ulong, List<ICoordinate>> orderedData;
List<ulong> orderedIndexes;
public SpatialIndex(IEnumerable<ICoordinate> data) {
orderedData = data.GroupBy(d => d.HilbertIndex()).ToSortedList(g => g.Key, g => g.ToList());
orderedIndexes = orderedData.Keys.ToList();
}
public ICoordinate FindNearest(ICoordinate aPoint) {
var hi = aPoint.HilbertIndex();
var nearestIndex = orderedIndexes.FindNearestIndex(hi);
var nearestGuess = orderedData.Values[nearestIndex][0];
var guessDist = (nearestGuess.Longitude - aPoint.Longitude) * (nearestGuess.Longitude - aPoint.Longitude) + (nearestGuess.Latitude - aPoint.Latitude) * (nearestGuess.Latitude - aPoint.Latitude);
if (nearestIndex > 0) {
var tryGuess = orderedData.Values[nearestIndex-1][0];
var tryDist = (tryGuess.Longitude - aPoint.Longitude) * (tryGuess.Longitude - aPoint.Longitude) + (tryGuess.Latitude - aPoint.Latitude) * (tryGuess.Latitude - aPoint.Latitude);
if (tryDist < guessDist) {
nearestGuess = tryGuess;
guessDist = tryDist;
}
}
var offsetPOI = new PointOfInterest(guessDist, guessDist);
var minhi = (aPoint.Minus(offsetPOI)).HilbertIndex();
var minhii = orderedIndexes.FindNearestIndex(minhi);
if (minhii > 0)
--minhii;
var maxhi = (aPoint.Plus(offsetPOI)).HilbertIndex();
var maxhii = orderedIndexes.FindNearestIndex(maxhi);
for (int j2 = minhii; j2 < maxhii; ++j2) {
var tryList = orderedData.Values[j2];
for (int j3 = 0; j3 < tryList.Count; ++j3) {
var y = tryList[j3];
var ydist = (y.Longitude - aPoint.Longitude) * (y.Longitude - aPoint.Longitude) + (y.Latitude - aPoint.Latitude) * (y.Latitude - aPoint.Latitude);
if (ydist < guessDist) {
nearestGuess = y;
guessDist = ydist;
}
}
}
return nearestGuess;
}
}
И подобный класс, использующий Кривую Мортона:
public class SpatialIndexMorton {
SortedList<ulong, List<ICoordinate>> orderedData;
List<ulong> orderedIndexes;
public SpatialIndexMorton(IEnumerable<ICoordinate> data) {
orderedData = data.GroupBy(d => d.MortonCode()).ToSortedList(g => g.Key, g => g.ToList());
orderedIndexes = orderedData.Keys.ToList();
}
public ICoordinate FindNearest(ICoordinate aPoint) {
var mc = aPoint.MortonCode();
var nearestIndex = orderedIndexes.FindNearestIndex(mc);
var nearestGuess = orderedData.Values[nearestIndex][0];
var guessDist = (nearestGuess.Longitude - aPoint.Longitude) * (nearestGuess.Longitude - aPoint.Longitude) + (nearestGuess.Latitude - aPoint.Latitude) * (nearestGuess.Latitude - aPoint.Latitude);
if (nearestIndex > 0) {
var tryGuess = orderedData.Values[nearestIndex-1][0];
var tryDist = (tryGuess.Longitude - aPoint.Longitude) * (tryGuess.Longitude - aPoint.Longitude) + (tryGuess.Latitude - aPoint.Latitude) * (tryGuess.Latitude - aPoint.Latitude);
if (tryDist < guessDist) {
nearestGuess = tryGuess;
guessDist = tryDist;
}
}
var offsetPOI = new PointOfInterest(guessDist, guessDist);
var minmc = (aPoint.Minus(offsetPOI)).MortonCode();
var minmci = orderedIndexes.FindNearestIndex(minmc);
if (minmci > 0)
--minmci;
var maxmc = (aPoint.Plus(offsetPOI)).MortonCode();
var maxmci = orderedIndexes.FindNearestIndex(maxmc);
for (int j2 = minmci; j2 < maxmci; ++j2) {
var tryList = orderedData.Values[j2];
for (int j3 = 0; j3 < tryList.Count; ++j3) {
var y = tryList[j3];
var ydist = (y.Longitude - aPoint.Longitude) * (y.Longitude - aPoint.Longitude) + (y.Latitude - aPoint.Latitude) * (y.Latitude - aPoint.Latitude);
if (ydist < guessDist) {
nearestGuess = y;
guessDist = ydist;
}
}
}
return nearestGuess;
}
}
И некоторые вспомогательные методы расширения:
public static class ListExt {
public static int FindNearestIndex<T>(this List<T> l, T possibleKey) {
var keyIndex = l.BinarySearch(possibleKey);
if (keyIndex < 0) {
keyIndex = ~keyIndex;
if (keyIndex == l.Count)
keyIndex = l.Count - 1;
}
return keyIndex;
}
}
public static class IEnumerableExt {
public static SortedList<TKey, TValue> ToSortedList<T, TKey, TValue>(this IEnumerable<T> src, Func<T, TKey> keySelector, Func<T, TValue> valueSelector) =>
new SortedList<TKey, TValue>(src.ToDictionary(keySelector, valueSelector));
}
Наконец, некоторые примеры кода, использующие это, с вашими ссылками вdata
, и ваши значения поиска в plds
:
var hilbertIndex = new SpatialIndex(data);
var ans = new (ICoordinate, ICoordinate)[lookups];
for (int j1 = 0; j1 < lookups; ++j1) {
ICoordinate pld = plds[j1];
ans[j1] = (pld, hilbertIndex.FindNearest(pld));
}
ОБНОВЛЕНИЕ: я изменил алгоритм поиска ближайшего, чтобы он брал самую близкую точку выше и ниже целевой точки индекса вместо простой попыткитот, что выше. Это обеспечивает еще одно приличное ускорение.