Как оптимизировать последовательность подсчета статистики и почему она работает так медленно
Вступление: я провел целый день в поисках того, почему моя обработка настолько медленная. Это было очень медленно на низких данных. Я проверил представления 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#. Это может помочь вам улучшить производительность, но не уверен, насколько это улучшение будет.