我想生成用于测试目的的大型xml文件,但我最终得到的代码非常慢,时间随着我写入文件的行数呈指数增长.下面的示例显示写入100行需要几毫秒,但写入1000行(在我的机器上)需要20多秒.我真的无法弄清楚是什么让这个慢,因为我认为写1000行不应该花那么长时间.另外,写入200行所需的时间约为写入100行的4倍,这是不好的.要运行代码,您可能需要更改代码的路径StreamWriter.
open System.IO
open System.Diagnostics
let xmlSeq = Seq.initInfinite (fun index -> sprintf "<author><name>name%d</name><age>%d</age><books><book>book%d</book></books></author>" index index index)
let createFile (seq: string seq) numberToTake fileName =
use streamWriter = new StreamWriter("C:\\tmp\\FSharpXmlTest\\FSharpXmlTest\\" + fileName, false)
streamWriter.WriteLine("<startTag>")
let rec internalWriter (seq: string seq) (sw:StreamWriter) i (endTag:string) =
match i with
| 0 -> (sw.WriteLine(Seq.head seq);
sw.WriteLine(endTag))
| _ -> (sw.WriteLine(Seq.head seq);
internalWriter (Seq.skip 1 seq) sw (i-1) endTag)
internalWriter seq streamWriter numberToTake "</startTag>"
let funcTimer fn =
let stopWatch = Stopwatch.StartNew()
printfn "Timing started"
fn()
stopWatch.Stop()
printfn "Time elased: %A" stopWatch.Elapsed
(funcTimer (fun () -> createFile xmlSeq 100 "file100.xml"))
(funcTimer (fun () -> createFile xmlSeq 1000 "file1000.xml"))
Run Code Online (Sandbox Code Playgroud)
您观察到O(n^2)操纵序列的二次行为.当您致电时Seq.skip,将创建一个全新的序列,因此您将隐式遍历其余部分.可以在/sf/answers/91438721/找到更详细的说明.
在此示例中,您不需要分解序列.通过以下方式取代内部功能:
let internalWriter (seq: string seq) (sw:StreamWriter) i (endTag:string) =
for node in Seq.take i seq do
sw.WriteLine(node)
sw.WriteLine(endTag)
Run Code Online (Sandbox Code Playgroud)
我可以在几分之一秒内写出10000行.
您可以通过删除此内部函数并将其主体复制到父函数来进一步重构.
如上所述,如果您需要分解序列,LazyList应该更好地使用.
pad在他的回答中指出了放缓的原因。另一种惯用的方法可能是使用Seq.unfold代替无限序列生成所需长度的序列,这使得代码非常简单:
let xmlSeq n = Seq.unfold (fun i ->
if i = 0 then None
else Some((sprintf "<author><name>name%d</name><age>%d</age><books><book>book%d</book></books></author>" i i i), i - 1)) n
let createFile seqLen fileName =
use streamWriter = new StreamWriter("C:\\tmp\\FSharpXmlTest\\" + fileName, false)
streamWriter.WriteLine("<startTag>")
seqLen |> xmlSeq |> Seq.iter streamWriter.WriteLine
streamWriter.WriteLine("</startTag>")
(funcTimer (fun () -> createFile 10000 "file10000.xml"))
Run Code Online (Sandbox Code Playgroud)
在我的笔记本电脑上生成 10000 个元素大约需要 500 毫秒。
小智 2
我想出了以下解决方案:
namespace FSharpBasics
module Program2 =
open System
open System.IO
open System.Diagnostics
let seqTest count : seq<string> =
let template = "<author>\
<name>Name {0}</name>\
<age>{0}</age>\
<books>\
<book>Book {0}</book>\
</books>\
</author>"
let row (i: int) =
String.Format (template, i)
seq {
yield "<authors>"
for x in [ 1..count ] do
yield row x
yield "</authors>"
}
[<EntryPoint>]
let main argv =
printfn "File will be written now"
let stopwatch = Stopwatch.StartNew()
File.WriteAllLines (@".\test.xml", seqTest 10000) |> ignore
stopwatch.Stop()
printf "Ended, took %f seconds" stopwatch.Elapsed.TotalSeconds
System.Console.ReadKey() |> ignore
0
Run Code Online (Sandbox Code Playgroud)
在我的笔记本电脑上,只需不到 90 毫秒即可创建包含 10,000 位作者的格式良好的test.xml文件。