F# неизменяемые структуры данных для высокочастотных потоковых данных в реальном времени

мы находимся в начале проекта f# с участием реального времени и исторического анализа потоковых данных. Данные содержатся в объекте c# (см. ниже) и отправляются как часть стандартного события .net. В режиме реального времени количество событий, которые мы обычно получаем, может сильно варьироваться от менее 1/С до более 800 событий в секунду на инструмент и, таким образом, может быть очень бурным. Типичный день может накапливать 5 миллионов строк / элементов в insturment

универсальный вариант структура данных события C# выглядит следующим образом:

public enum MyType { type0 = 0, type1 = 1}

public class dataObj
{
    public int myInt= 0;
    public double myDouble;
    public string myString;
    public DateTime myDataTime;
    public MyType type;
    public object myObj = null;

}

мы планируем использовать эту структуру данных в f# двумя способами:

  1. исторический анализ с использованием контролируемого и неконтролируемого машинного обучения (CRFs, модели кластеризации и т. д.)
  2. классификация потоков данных в реальном времени с использованием вышеуказанных моделей

структура данных должна быть в состоянии расти, поскольку мы добавляем больше событий. Это исключает array<t> потому что это не позволяет изменение размера, хотя его можно использовать для исторического анализа. Структура данных также должна иметь возможность быстрого доступа к последним данным и в идеале должна иметь возможность перехода к данным X точек назад. Это исключает Lists<T> из-за линейного времени поиска и потому, что нет случайного доступа к элементам, просто "только вперед" обход.

по данным этот пост, Set<T> может быть хорошим выбором...

> " ...Ванильный набор делает больше, чем адекватная работа. Я бы предпочел "набор" над "списком", чтобы у вас всегда был доступ O(lg n) к самым большим и самым маленьким элементам, позволяя вам заказывать свой набор по дате/времени вставки для эффективного доступа к самым новым и старым элементам..."

EDIT:ответ Инь Чжу дал мне дополнительную ясность в том, что я спрашивал. Я отредактировал оставшуюся часть сообщения, чтобы отразить это. Кроме того, предыдущая версия этого вопроса была замутнена введением требований к исторический анализ. Я опустил их.

вот разбивка шагов процесса в реальном времени:

  1. событие в реальном времени получено
  2. это событие помещается в структуру данных. это структура данных, которую мы пытаемся определить. Должно ли это быть Set<T>, или какая-то другая структура?
  3. подмножество элементов либо извлекается, либо каким-то образом повторяется с целью создания объектов. Этот будут либо последние n строк/элементов структуры данных (т. е. последние 1000 событий или 10 000 событий) или все элементы за последние X секунд / минут (i.e все события за последние 10 минут). В идеале нам нужна структура, которая позволяет нам делать это эффективно. В частности, имеет значение структура данных, которая позволяет произвольный доступ n-го элемента без итерации через все остальные элементы.
  4. функции для модели генерируются и отправляются на модели оценка.
  5. мы можем обрезать структуру данных старых данных для повышения производительности.

таким образом, вопрос в том, какая структура данных лучше всего использовать для хранения потоковых событий в реальном времени, которые мы будем использовать для сгенерированных функций.

4 ответов


следует считать FSharpx.Коллекции.Вектор. Vector предоставит вам Массивоподобные функции, включая индексированный поиск и обновление O(log32(n)), который находится на расстоянии плевка O(1), а также добавление новых элементов в конец вашей последовательности. Существует еще одна реализация вектора, которую можно использовать из F# at Твердый Вектор. Очень хорошо документировано, и некоторые функции выполняют до 4X быстрее в больших масштабах (количество элементов > 10K). Обе реализации выполните очень хорошо до и по возможности за элементами 1M.


в своем ответе Джек Фокс предлагает использовать либо FSharpx.Сборники Vector<'T> или сплошная Vector<'t> Грега Розенбаума (https://github.com/GregRos/Solid). Я подумал, что мог бы дать немного назад сообществу, предоставив Инструкции о том, как встать и работать с каждым из них.

использование FSharpx.Коллекции.Вектор

процесс довольно прямолинейный:

  1. скачать FSharpx.Основной пакет NuGet использование либо консоли Project Manager, либо пакетов NuGet Manager для решения. Оба находятся в Visual Studio - > tools - > Library Manager.
  2. если вы используете его в файле скрипта F#, добавьте #r "FSharpx.Core.dll". Возможно, вам придется использовать полный путь.

использование:

open FSharpx.Collections

let ListOfTuples = [(1,true,3.0);(2,false,1.5)] 
let vector = ListOfTuples |> Vector.ofSeq

printfn "Last %A" vector.Last
printfn "Unconj %A" vector.Unconj
printfn "Item(0) %A" (vector.[0])
printfn "Item(1) %A" (vector.[1])
printfn "TryInitial %A" dataAsVector.TryInitial
printfn "TryUnconj %A" dataAsVector.Last

через сплошную.Вектор

получение настройки для использования Solid Vector<'t> - это немного сложнее. Но твердая версия имеет гораздо более удобную функциональность и как Jack отмечено, имеет ряд преимуществ. Он также имеет много полезной документации.

  1. вам нужно будет загрузить решение visual studio изhttps://github.com/GregRos/Solid
  2. после того, как вы загрузили его, вам нужно будет построить его, так как нет готовых к использованию предварительно построенной dll.
  3. если вы похожи на меня, вы можете столкнуться с рядом отсутствующих зависимостей, которые мешают решения строится. В моем случае, они все они были связаны с фреймворками тестирования nuit (я использую другой). Просто работайте через загрузку / добавление каждой из зависимостей, пока решения не будут построены.
  4. как только это будет сделано, и решение будет построено, у вас будет блестящее новое твердое тело.dll в папке Solid/Solid / bin. Вот тут я ошибся. Это основная dll и достаточно только для использования C#. Если только включить ссылку на твердое тело.dll вы сможете создать вектор в f#, но фанки вещи будут с этого момента.
  5. чтобы использовать эту структуру данных в F#, вам необходимо указать как Solid.dll и Solid.FSharp.dll который находится в . Вам понадобится только одно открытое заявление -> open Solid

вот некоторый код, показывающий использование в файле скрипта F#:

#r "Solid.dll"
#r "Solid.FSharp.dll" // don't forget this reference

open Solid

let ListOfTuples2 = [(1,true,3.0);(2,false,1.5)] 
let SolidVector = ListOfTuples2 |> Vector.ofSeq

printfn "%A" SolidVector.Last
printfn "%A" SolidVector.First
printfn "%A" (SolidVector.[0])
printfn "%A" (SolidVector.[1])
printfn "Count %A" SolidVector.Count

let test2 = vector { for i in {0 .. 100} -> i }

Предположим ваш dataObj содержит уникальное поле ID, тогда любая структура данных набора будет в порядке для вашей работы. Неизменяемые структуры данных в основном используются для кода функционального стиля или персистентности. Если вам не нужны эти два, вы можете использовать HashSet<T> или SortedSet<T> в библиотеке коллекции .Net.

некоторая оптимизация потока может быть полезна, например, сохранение фиксированного размера Queue<T>для самых последних объектов данных в потоке и храните более старые объекты в более тяжелом весе набор. Я бы предложил бенчмаркинг перед переключением на такие гибридные решения структуры данных.

Edit:

после более тщательного чтения ваших требований я обнаружил, что вам нужна очередь с доступным для пользователя индексированием или обратным перечислителем. В этой структуре данных ваши операции извлечения объектов(например, среднее/сумма и т. д.) стоят O (n). Если вы хотите выполнить некоторые операции в O (log n), вы можете использовать более сложные структуры данных, например интервальные деревья или списки пропусков. Однако вам придется реализовать эти структуры данных самостоятельно, так как вам нужно хранить метаданные в узлах дерева, которые находятся за API сбора.


это событие помещается в структуру данных. Это структура данных, которую мы пытаемся определить. Должен ли это быть набор, очередь или какая-то другая структура?

трудно сказать без дополнительной информации.

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

если ваши данные могут прийти в не в порядке, и вы нуждаетесь в них переупорядочено, тогда вам нужна приоритетная очередь или индексированная коллекция.

до более чем 800 событий в секунду

те весьма ручные требования производительности для тарифа ввода.

подмножество элементов либо извлекается, либо каким-то образом повторяется с целью создания объектов. Это будут либо последние n строк/элементов структуры данных (т. е. последние 1000 событий или 10 000 событий) или все элементы за последние X сек / мин (i.e все события за последние 10 минут). В идеале нам нужна структура, которая позволяет нам делать это эффективно. В частности, имеет значение структура данных, которая позволяет произвольный доступ n-го элемента без итерации через все остальные элементы.

если вы только когда-либо хотите элементы в начале, почему вы хотите случайный доступ? Вы действительно хотите случайный доступ по индексу или вы действительно хотите случайный доступ по какому-то другому ключу, например время?

из того, что вы сказали, я бы предложил использовать обычный F# Map введено по индексу, поддерживаемому MailboxProcessor это может добавить новое событие и получить объект, который позволяет индексировать все события, т. е. обернуть Map в объекте, который предоставляет свою собственную Item свойство и реализация IEnumerable<_>. На моей машине, что простое решение занимает 50 строк кода и может обрабатывать около 500 000 событий в секунду.