Как оптимизировать последовательность подсчета статистики и почему она работает так медленно

Вступление: я провел целый день в поисках того, почему моя обработка настолько медленная. Это было очень медленно на низких данных. Я проверил представления SQL, процедуры и логику linq- и все они работали отлично. но потом я увидел, что для обработки этой мелочи требуются годы.

member X.CountStatistics()= 
    linq.TrueIncidents
    |> PSeq.groupBy (fun v -> v.Name)
    |> PSeq.map (fun (k, vs) -> k, PSeq.length vs)
    |> Array.ofSeq

Он просто считает сгруппированные значения, но сколько времени он тратит! около 10 секунд на легком столе,

Должно быть что-то злое, рекурсивное, но я не вижу этого...

Как я могу сделать эту операцию "немного быстрее" или перекодировать ее в linq-to-sql?

3 ответа

Решение

Текущая версия поддержки F# LINQ немного ограничена.

Я думаю, что лучший способ написать это - пожертвовать некоторой элегантностью использования F# для этого и записать его как хранимую процедуру в SQL. Затем вы можете добавить хранимую процедуру к вашему linq контекст данных и вызывайте его красиво, используя сгенерированный метод. Когда F# LINQ немного улучшится в будущем, вы можете изменить его обратно:-).

Учитывая PSeq пример - насколько я знаю, была некоторая проблема с эффективностью, потому что методы не были встроены (благодаря встроенному компилятору удалось выполнить некоторую дополнительную оптимизацию и устранить некоторые накладные расходы). Вы можете попробовать загрузить исходный код и добавить inline в map а также groupBy,

Если я правильно понимаю, TrueIncidents - это таблица в БД, вы помещаете все содержимое в клиентское приложение, чтобы выполнить некоторую группировку и подсчет. Если TrueIncidents - это большая таблица, то эта операция всегда будет медленной, поскольку вы перемещаете большой объем данных. "Правильный" способ сделать это - использовать базу данных, как вы предлагаете использовать linq to SQL, или как Томас предлагает использовать хранимую процедуру.

Что касается PSeq, я не думаю, что встраивание будет иметь большое значение. Распараллеливание сопряжено с дополнительными затратами, и для того, чтобы эти издержки амортизировали список, он должен быть относительно большим, а операция, которую вы выполняете над каждым элементом в списке, должна быть значительной. Распараллеливание может стоить небольшого списка, если операция, которую вы выполняете над каждым элементом, очень дорогая, однако обратное действительно верно; даже если список очень большой, распараллеливание небольшой операции не будет стоить накладных расходов. Таким образом, проблема в этом случае заключается в том, что операция, которую вы выполняете для каждого элемента в списке, слишком мала, поэтому стоимость распараллеливания всегда будет замедлять операцию. Чтобы увидеть это, рассмотрим следующую программу на C#, где мы выполняем простое добавление в список с 10 миллионами элементов, вы увидите, что параллельная версия всегда работает медленно (ну, на машине, на которой я сейчас работаю, которая имеет два ядра, я думаю, на машине с большим количеством ядер результат может отличаться).

    static void Main(string[] args)
    {
        var list = new List<int>();
        for (int i = 0; i < 10000000; i++)
        {
            list.Add(i);
        }

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        var res1 = list.Select(x => x + 1);
        foreach (var i in res1)
        {

        }
        stopwatch.Stop();
        Console.WriteLine(stopwatch.Elapsed);
        // 00:00:00.1950918 sec on my machine

        stopwatch.Start();
        var res2 = list.Select(x => x + 1).AsParallel();
        foreach (var i in res2)
        {

        }
        stopwatch.Stop();
        Console.WriteLine(stopwatch.Elapsed);
        // 00:00:00.3748103 sec on my machine
    }

Как уже упоминалось в других ответах, если вы принесете большой объем данных из базы данных, а затем проведете некоторые вычисления для этого большого набора данных, это будет дорого (я думаю, что часть ввода-вывода будет дороже, чем часть вычислений). В вашем конкретном случае кажется, что вы хотите счетчик для каждого имени инцидента. Одним из подходов для этого может быть использование F# linq-sql, просто вывести "имена" инцидента из базы данных (никакой другой столбец, поскольку они вам не нужны), а затем выполнить группирование и отображение, работающее в F#. Это может помочь вам улучшить производительность, но не уверен, насколько это улучшение будет.

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