i创建记录类型如下:
type CombEmp =
{
empid:int
empname:string
email:string
}
let defCombEmp:CombEmp =
{
empid = 0
empname = ""
email = ""
}
i然后创建记录实例:
let chrE1 = {defCombEmp with empid = 100; empname = "Wayne Rooney"; email = "wroo@mun.com"}
let chrE2 = {defCombEmp with empid = 100; empname = "Wayne R"; email = "war@mun.com"}
let chrE3 = {defCombEmp with empid = 100; empname = "Wayne R"; email = "rooney@mun.com"}
然后,我使用上述实例创建了一个65-70记录的列表:
let hrLst = [|chrE1;chrE2;chrE3;chrE1;chrE2;chrE3;chrE1; ...... |] |> Array.toList
我现在已经编写了如下所示。
功能GetByteCount
获取序列化数据的大小(在此处使用newtonsoft)。
该功能loop1
使用上述hrLst
创建了约500K长的长列表。
函数loop2
在每次迭代中都将输入序列降低1000,并将其剩余列表调用GetByteCount
let GetByteCount data = //can improve this algorithm?
let stopWatch = System.Diagnostics.Stopwatch.StartNew()
let x = data
|> JsonConvert.SerializeObject
|> Encoding.UTF8.GetByteCount
stopWatch.Stop()
Console.WriteLine("time reqd: " + stopWatch.Elapsed.TotalMilliseconds.ToString() + " milliseconds")
x
let PerfTestLoop() =
let rec loop1 ctr l =
if ctr%100 = 0 then Console.WriteLine("" + ctr.ToString())
match ctr with
|500 -> l
|_ ->
let l2 = l @ hrLst
loop1 (ctr+1) l2
let l = loop1 1 hrLst
let len = l.Length
let s = l @ l |> List.toSeq
Console.WriteLine("input length: " + (Seq.length s).ToString())
Console.WriteLine("Start Measuring ..")
let stopWatch = System.Diagnostics.Stopwatch.StartNew()
let rec loop2 ctr s =
match ctr with
|100 -> s |> GetByteCount
|_ ->
let news = Seq.skip 2000 s
let size = news |> GetByteCount
loop2 (ctr+1) news
let x = loop2 1 s
let res = PerfTestLoop() // becomes slow gradually
观察是,即使序列的大小正在减小,在loop2
的每次迭代中执行GetByteCount
的执行时间都会增加!为什么会发生这种情况?在任务管理器中,CPU和内存使用率保持稳定。是否有其他方法可以找到数据的字节数或减少执行GetByteCount
所需的时间?
如果在loop2
中i删除Seq.skip
行并在每个迭代中使用相同的序列,则每次迭代所需的时间相似,并且不会变化太大。
此代码有许多可能的改进,但是这里的主要问题不是根据其语义和性能中的相对优点和缺点来理解何时选择列表,数组或SEQ。/p>
f#列表是一个不变的单链接列表,它快速添加和从正面添加和删除项目很快。在串联时(@
运算符)或以某个索引访问项目。
f#数组是.NET数组:它是可变的,并且具有快速的索引访问等。但是,大多数用于使用数组的F#功能都会避免突变并制作副本。
seq
是.NET IEnumerable<T>
。这是您可以从头开始列举的序列。这些值是根据序列消费者需要计算的,并且计算可能很昂贵。每个值可能取决于先前的值,具体取决于seq
的实现,这就是为什么如果您创建具有跳过值的新序列并将其传递给其他地方,那么新的呼叫站点仍然需要重新评估跳过的值以获取以获取它实际使用的。在F#中解决此问题的一种方法是使用Seq.cache
。这将使正常的SEQ变成一个评估时缓存每个项目的SEQ,以免重复访问不会引起重计。
那么,为什么首先需要使用seq
?您需要一个list
,这并不懒惰,因此您将所有这些数据都藏在内存中。您做let s = l @ l |> List.toSeq
。将其更改为List.toArray
,然后在以后使用Array.skip
将其更快地更快。
,这也可能有助于将所有列表更改为数组,因为您不利用列表的好处。如果将hrLst
作为数组保持,则可以完全删除loop1
,然后执行此操作:Array.replicate 500 hrLst |> Array.concat
通常,我建议您对F#集合非常熟悉:列表,数组,SEQ,MAP和SET。浏览这些模块中的功能及其文档,因为它们几乎在所有F#代码中都会有用。内置功能通常会消除使用递归的需求,并导致更简单的代码。我怀疑loop2
也可以用Array.chunkBySize
的某些使用来代替,但我不清楚您的代码要做什么。