Оптимизация mgo для поиска пути

Я реализовал алгоритм A* в Go, чтобы найти путь между двумя координатами на карте. Данные карты извлекаются с помощью mgo из коллекции MongoDB.

Однако это очень медленно. Он составляет около 4 секунд для маршрута на 1000 метров. Я рассчитал время для различных частей алгоритма и пришел к выводу, что узкое место находится в выборке из базы данных. Или, если быть точным: в преобразовании бинарных данных в структуру данных, которую понимает Go.

Я стараюсь делать как можно меньше запросов и многопотоковые запросы, чтобы сделать это быстрее, и это в определенной степени помогает. Но это не так быстро, как хотелось бы.

Кажется, я делаю что-то принципиально неправильное. Любые предложения вообще будут полезны.

Структура данных в mongoDB: (узлы получены из OSM)

{ "_id" : NumberLong(194637483),
  "lat" : 55.7079899, 
  "lon" : 13.3756414,
  "neighbours" : [ NumberLong(1566264689), NumberLong(1566264806) ] 
}

Структура данных в Go

type Node struct {
    ID         int64   `bson:"_id" json:"id"`
    Lat        float64 `bson:"lat" json:"lat"`
    Lon        float64 `bson:"lon" json:"lon"`
    Neighbours []int64 `bson:"neighbours" json:"neighbours"`
}

Код для получения части данных:

func addToBufferLong(buffer *WriteLockMap, session *mgo.Session, at, goal geo.Node, lowerLat, higherLat, lowerLon, higherLon float64) {
    c := session.DB("collectionName").C("nodes")
    query := c.Find(bson.M{"lat": bson.M{"$gt": lowerLat, "$lt": higherLat}, "lon": bson.M{"$gt": lowerLon, "$lt": higherLon}})

    var nodes []geo.Node
    query.All(&nodes)
    for _, node := range nodes {
        tmp := PathNode{0, node.DistanceTo(goal), 0, node}
        buffer.Lock()
        buffer.m[node.ID] = tmp
        buffer.Unlock()
    }
}

Редактировать:

Стратегия многопоточности основана на разделении области, которую я хочу запросить, на 4 разных квадрата, квадранта, если хотите, и выполнении их отдельно с помощью addToBufferLong(...)

Последние распечатки:

> time GOMAXPROCS=8 ./toystar
Starting A star
Thread A, count: 19657, db-fetch: 0.122792104s, parsing: 0.934650055s
Thread B, count: 19574, db-fetch: 0.274384302s, parsing: 1.196350664s
Thread C, count: 4197, db-fetch: 0.388197823s, parsing: 0.930109241s
Thread D, count: 9900, db-fetch: 0.540008325s, parsing: 0.93963728s

Total database fetch:  1.534268099 s
Total A star (with fetches):  1.854748244

real    0m1.907s

где db-fetch измеряет время, необходимое для выполнения строки, которая начинается с query := c.Find(...), а синтаксический анализ измеряет время, необходимое для выполнения query.All(&nodes)

Редактировать 2:

Мне удалось значительно сократить время выполнения с помощью других пользователей переполнения стека. Текущие распечатки:

> time GOMAXPROCS=8 ./toystar

Starting A star
Thread A: 0.210783141s
Thread B: 0.380938949s
Thread C: 0.441447793s
Thread D: 0.507361847s

Total database fetch:  0.507543875 s
number of fetches:  1
Total A star:  0.826343287s

real    0m0.860s

Основное отличие заключается в многопоточной стратегии и использовании *mgo.Iter вместо query.All(&nodes).


person AAAton    schedule 15.10.2015    source источник
comment
Я думаю, вы упустили самое важное, (geo.Node).DistanceTo(geo.Node). Кроме того, какова ваша многопоточная стратегия, то есть как вы распределяете работу между рабочими?   -  person thwd    schedule 15.10.2015
comment
DistanceTo — это всего лишь небольшой расчет. Это не очень быстро, но и не совсем узкое место.   -  person AAAton    schedule 15.10.2015
comment
В этом случае узкое место не очень очевидно, поэтому я бы сказал, что profile использует runtime/pprof (или альтернативный вариант ).   -  person thwd    schedule 15.10.2015
comment
Я добавил измерения, сделанные вручную с помощью time.Now().UnixNano(). Спасибо за интерес!   -  person AAAton    schedule 15.10.2015


Ответы (1)


Из имеющейся информации могу сделать следующий вывод:

  1. Ваша машина кажется медленной, возможно, это встроенное устройство?

Этап, который вы назвали db-fetch, на самом деле не обращается к базе данных. Единственное, что делает c.Find(...), — это создает значение *mgo.Query. Метод состоит из 6 строк. Это не должно занять больше миллисекунды. Если нет разногласий по внутреннему состоянию сеанса объекта базы данных, что, похоже, не так, потому что вы используете только 4 горутины.

  1. Узким местом кажется сеть и/или отражение

query.All(&nodes) — это место, где фактически выполняется запрос в вашей базе данных. Кроме того, на этом этапе выделяется необходимый фрагмент узлов, а затем итеративно декодируется bson в определение вашей структуры посредством отражения.

  1. Вы можете попробовать одну вещь: *mgo.iter

Вместо query.All(...) вы можете использовать query.Iter(), чтобы получить *mgo.Iter и последовательно перебирать наборы данных. Это может повысить производительность за счет лучшего распределения сетевой нагрузки во времени.

  1. Используйте геопространственную индексацию, Mongo ее поддерживает.

См. документацию. Может быть, вы уже делаете это. Если нет, это может улучшить время поиска.

  1. Разделите ваши квадранты на подквадранты, рекурсивно.

Я думаю, что это очевидно. Разделяй и властвуй, да? :)

person thwd    schedule 15.10.2015
comment
1. Моя машина работает медленно. Это хромбук с двухъядерным процессором. Алгоритм примерно в два раза быстрее на iMac или аналогичном. 2. На самом деле это имеет смысл. При удалении измерений для query.Count() я пропускаю некоторые из них во время выполнения. 3. Я попробую это и вернусь. 4. Пробовали геопространственную индексацию. Это было намного медленнее. Спасибо. - person AAAton; 15.10.2015
comment
Я изменил свою стратегию многопоточности. Чтобы разделить область только в одном направлении. Так что только разбивая область по долготе и сохраняя широту нетронутой. Это, казалось, ускорило запросы по сравнению с выполнением квадратов. Может быть, это как-то связано с внутренним порядком mongoDB, или, может быть, это просто случайность в моем конкретном тестовом примере. - person AAAton; 15.10.2015