我们正处于f#项目的开端,该项目涉及流数据的实时和历史分析。数据包含在c#对象中(见下文),并作为标准.net事件的一部分发送。实时地,我们通常接收的事件的数量可以从每个仪器每秒大约800个事件的不到1 /秒到更高的变化很大,因此可以是非常突发的。典型的一天可能会累积500万行/每个元素
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#中使用此数据结构:
随着我们添加更多事件,数据结构需要能够增长。这排除了array<t>
,因为它不允许调整大小,尽管它可以用于历史分析。数据结构还需要能够快速访问最近的数据,理想情况下需要能够跳回到数据x点。这排除Lists<T>
因为线性查找时间,并且因为没有随机访问元素,只是“仅向前”遍历。
根据this post,Set<T>
可能是个不错的选择......
以下是实时流程步骤的细分:
Set<T>
还是其他结构?所以问题是什么是用于存储我们将用于生成功能的实时流事件的最佳数据结构。
答案 0 :(得分:11)
您应该考虑FSharpx.Collections.Vector。矢量&lt; T&GT;将为您提供类似于数组的功能,包括索引的O(log32(n))查找和更新,它在O(1)的吐出距离内,以及在序列的末尾添加新元素。 Vector的另一个实现可以在Solid Vector的F#中使用。记录很好,一些功能在大规模上的速度提高了4倍(元素数> 10K)。两种实现都可以很好地执行,甚至可能超过1M元素。
答案 1 :(得分:10)
在他的回答中,杰克福克斯建议使用FSharpx.Collections Vector<'T>
或Greg Rosenbaum(https://github.com/GregRos/Solid)的实体Vector<'t>
。我想我可以通过提供如何启动和运行每个人的说明来回馈社区。 p>
这个过程非常简单:
#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>
的设置更复杂一些。但是Solid版本具有更多方便的功能,正如杰克指出的那样,它具有许多性能优势。它还有很多有用的文档。
Solid.dll
文件夹中的Solid.FSharp.dll
和\Solid\SolidFS\obj\Debug\
。您只需要一个公开声明 - &gt; 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 }
答案 2 :(得分:5)
假设您的dataObj
包含唯一的ID字段,那么任何设置的数据结构都适合您的工作。不可变数据结构主要用于功能样式代码或持久性。如果您不需要这两个,则可以在.Net集合库中使用HashSet<T>
或SortedSet<T>
。
某些特定于流的优化可能是有用的,例如,为流中的最新数据对象保持固定大小Queue<T>
并将较旧的对象存储在较重的权重集中。在转换到这种混合数据结构解决方案之前,我建议进行基准测试。
修改:
在更仔细地阅读您的要求之后,我发现您想要的是具有用户可访问索引或后向枚举器的队列。在此数据结构下,您的特征提取操作(例如平均值/总和等)花费O(n)。如果要在O(log n)中执行某些操作,可以使用更高级的数据结构,例如区间树或跳过列表。但是,您需要自己实现这些数据结构,因为您需要将元信息存储在集合API后面的树节点中。
答案 3 :(得分:5)
此事件位于数据结构中。这是我们试图确定的数据结构。它应该是Set,Queue还是其他结构?
没有更多信息很难说。
如果您的数据以递增的顺序进入时间戳(即它们永远不会出现故障),那么您可以使用某种队列或可扩展数组。
如果您的数据无法正常运行并且需要重新排序,那么您需要优先级队列或索引集合。
每秒大约800个事件
这些对插入率的性能要求非常高。
为了进行特征生成,要么提取元素的子集,要么以某种方式迭代。这将是数据结构的最后n行/元素(即,最后1000个事件或10,000个事件)或最后x秒/分钟中的所有元素(即最后10分钟内的所有事件)。理想情况下,我们需要一种允许我们有效地执行此操作的结构。特别是,允许随机访问第n个元素而不迭代所有其他元素的数据结构是有价值的。
如果您只想要开头附近的元素,为什么要随机访问?你真的想通过索引进行随机访问吗?或者你真的希望通过其他一些密钥随机访问,比如时间吗?
根据您的说法,我建议使用一个普通的F#Map
键入由MailboxProcessor
维护的索引,该索引可以追加新事件并检索允许所有事件发生的对象索引,即将Map
包装在提供其自己的Item
属性和IEnumerable<_>
实现的对象中。在我的机器上,简单的解决方案需要50行代码,每秒可以处理大约500,000个事件。