Как можно быстрее выполнить этот запрос LINQ для Enumerable DataTable данных GTFS?

Я работаю с данными GTFS для системы метро MTA в Нью-Йорке. Мне нужно найти время остановки для каждого маршрута на определенной остановке. Для этого я получаю время остановки из имеющейся у меня таблицы данных StopTimes для определенного идентификатора stop_id. Я только хочу время остановки между настоящим моментом и следующими 2 часами.

Затем мне нужно искать поездку для каждого времени остановки, используя значение trip_id. Из этой поездки мне нужно искать маршрут, используя значение route_id, чтобы получить название или номер маршрута для времени остановки.

Вот подсчеты для каждой таблицы данных: StopTimes(522712), Trips(19092), Routes(27).

Прямо сейчас это может занять от 20 до 40 секунд. Как я могу ускорить это? Любые и все предложения приветствуются. Спасибо!

foreach (var r in StopTimes.OrderBy(z => z.Field<DateTime>("departure_time").TimeOfDay)
                           .Where(z => z.Field<string>("stop_id") == stopID &&
                                  z["departure_time"].ToString() != "" &&
                                  z.Field<DateTime>("departure_time").TimeOfDay >= DateTime.UtcNow.AddHours(-5).TimeOfDay &&
                                  z.Field<DateTime>("departure_time").TimeOfDay <= DateTime.UtcNow.AddHours(-5).AddHours(2).TimeOfDay))
        {
            var trip = (from z in Trips
                        where z.Field<string>("trip_id") == r.Field<string>("trip_id") &&
                              z["route_id"].ToString() != ""
                        select z).Single();

            var route = (from z in Routes
                         where z.Field<string>("route_id") == trip.Field<string>("route_id")
                         select z).Single();

            // do stuff (not time-consuming)
        }

3 ответа

Решение

Попробуй это:

var now = DateTime.UtcNow;
var tod0 = now.AddHours(-5).TimeOfDay;
var tod1 = now.AddHours(-5).AddHours(2).TimeOfDay;

var sts =
    from st in StopTimes
    let StopID = st.Field<string>("stop_id")
    where StopID == stopID
    where st["departure_time"].ToString() != ""
    let DepartureTime = st.Field<DateTime>("departure_time").TimeOfDay
    where DepartureTime >= tod0
    where DepartureTime >= tod1
    let TripID = st.Field<string>("trip_id")
    select new
    {
        StopID,
        TripID,
        DepartureTime,
    };

Обратите внимание, что нет orderby в этом запросе и что мы возвращаем анонимный тип. Для выполнения кода "делать вещи (не отнимающего много времени)" может потребоваться добавить еще несколько свойств.

Тот же подход происходит для Trips & Routes,

var ts =
    from t in Trips
    where t["route_id"].ToString() != ""
    let TripID = t.Field<string>("trip_id")
    let RouteID = t.Field<string>("route_id")
    select new
    {
        TripID,
        RouteID,
    };

var rs =
    from r in Routes
    let RouteID = r.Field<string>("route_id")
    select new
    {
        RouteID,
    };

Так как вы получаете одну запись для каждого просмотра, то с помощью ToDictionary(...) хороший выбор для использования.

var tripLookup = ts.ToDictionary(t => t.TripID);
var routeLookup = rs.ToDictionary(r => r.RouteID);

Теперь ваш запрос выглядит так:

var query = from StopTime in sts.ToArray()
            let Trip = tripLookup[StopTime.TripID]
            let Route = routeLookup[Trip.RouteID]
            orderby StopTime.DepartureTime
            select new
            {
                StopTime,
                Trip,
                Route,
            };

Обратите внимание, что я использовал .ToArray() и я положил orderby прямо в конце.

И вы запускаете свой код так:

foreach (var q in query)
{
    // do stuff (not time-consuming)
}

Позвольте мне знать, если это помогает.

Я бы сделал Dictionary<int, Trip> из поездок, где ключ является trip_idи Dictionary<int, Route> от Routes где ключ route_id, Ваш код перебирает 19092 элементов в Trips один раз для каждого из элементов в отфильтрованном IEnumerable<StopTime>, То же самое для Routes, но, по крайней мере, там всего 27 предметов.

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

на самом деле, если присмотреться к нему, первый словарь будет Dictionary<int, int> где значение является route_id, И учитывая отношения один к одному между trip_id а также route_id Вы могли бы просто построить Dictionary<trip_id, Route> и сделать один поиск.

Это помогает понять отложенное выполнение запроса, поэтому вы можете принимать индивидуальные решения о том, как оптимизировать время выполнения. Вот хорошее сообщение в блоге, которое поможет вам начать: http://ox.no/posts/linq-vs-loop-a-performance-test

Другие вопросы по тегам