为什么F#中的printf这么慢?

时间:2011-06-06 07:08:58

标签: performance f#

我对F#的printf有多慢感到非常惊讶。我有许多C#程序处理大型数据文件并写出许多CSV文件。我最初使用fprintf writer "%s,%d,%f,%f,%f,%s"认为这样做很简单,效率也很合理。

然而,过了一段时间我有点厌倦了等待文件处理。 (我有4GB的XML文件可以通过它们写出来。)。

当我通过分析器运行我的应用程序时,我惊讶地发现printf是一种非常慢的方法。

我将代码更改为不使用printf,现在性能要好得多。 Printf性能破坏了我的整体应用程序性能。

举个例子,我原来的代码是:

fprintf sectorWriter "\"%s\",%f,%f,%d,%d,\"%s\",\"%s\",\"%s\",%d,%d,%d,%d,\"%s\",%d,%d,%d,%d,%s,%d"
    sector.Label sector.Longitude sector.Latitude sector.RNCId sector.CellId
    siteName sector.Switch sector.Technology (int sector.Azimuth) sector.PrimaryScramblingCode
    (int sector.FrequencyBand) (int sector.Height) sector.PatternName (int sector.Beamwidth) 
    (int sector.ElectricalTilt) (int sector.MechanicalTilt) (int (sector.ElectricalTilt + sector.MechanicalTilt))
    sector.SectorType (int sector.Radius)

我已将其更改为以下

seq {
    yield sector.Label; yield string sector.Longitude; yield string sector.Latitude; yield string sector.RNCId; yield string sector.CellId; 
    yield siteName; yield sector.Switch; yield sector.Technology; yield string (int sector.Azimuth); yield string sector.PrimaryScramblingCode;
    yield string (int sector.FrequencyBand); yield string (int sector.Height); yield sector.PatternName; yield string (int sector.Beamwidth); 
    yield string (int sector.ElectricalTilt); yield string (int sector.MechanicalTilt); 
    yield string (int (sector.ElectricalTilt + sector.MechanicalTilt));
    yield sector.SectorType; yield string (int sector.Radius)
}
|> writeCSV sectorWriter

助手功能

let writeDelimited delimiter (writer:TextWriter) (values:seq<string>) =
    values
    |> Seq.fold (fun (s:string) v -> if s.Length = 0 then v else s + delimiter + v) ""
    |> writer.WriteLine

let writeCSV (writer:TextWriter) (values:seq<string>) = writeDelimited "," writer values

我正在写出大约30,000行的文件。没什么特别的。

4 个答案:

答案 0 :(得分:11)

我不确定它有多重要,但是......

检查printf的代码:

https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/printf.fs

我看到了

// The general technique used this file is to interpret
// a format string and use reflection to construct a function value that matches
// the specification of the format string.  

我认为“反思”这个词可能会回答这个问题。

printf非常适合编写简单的类型安全输出,但如果你想在内部循环中获得良好的性能,你可能希望使用较低级别的.NET API来编写输出。我还没有完成自己的基准测试。

答案 1 :(得分:10)

TextWriter已经缓冲了它的输出。我建议使用Write一次输出一个值,而不是格式化整行并将其传递给WriteLine。在我的笔记本电脑上,使用你的功能写入100,000行需要将近一分钟,而使用以下功能,它会在半秒内运行。

let writeRow (writer:TextWriter) siteName (sector:Sector) = 
  let inline write (value:'a) (delim:char) = 
    writer.Write(value)
    writer.Write(delim)
  let inline quote s = "\"" + s + "\""
  write (quote sector.Label) ','
  write sector.Longitude ','
  write sector.Latitude ','
  write sector.RNCId ','
  write sector.CellId ','
  write (quote siteName) ','
  write (quote sector.Switch) ','
  write (quote sector.Technology) ','
  write (int sector.Azimuth) ','
  write sector.PrimaryScramblingCode ','
  write (int sector.FrequencyBand) ','
  write (int sector.Height) ','
  write (quote sector.PatternName) ','
  write (int sector.Beamwidth) ','
  write (int sector.ElectricalTilt) ','
  write (int sector.MechanicalTilt) ','
  write (int (sector.ElectricalTilt + sector.MechanicalTilt)) ','
  write sector.SectorType ','
  write (int sector.Radius) '\n'

答案 2 :(得分:8)

现在F#3.1已经预览发布,据称printf的性能提高了40倍。你可能想看看这个:

  

F#3.1编译器/库添加

     

Printf性能

     

F#3.1核心库看到了printf系列的改进性能   用于类型安全格式化的函数。例如,使用打印   以下格式字符串现在可以更快地运行 40x (尽管您的   确切的里程可能会有所不同):

     

sprintf "%d: %d, %x %X %d %d %s"您的代码无需更改   利用这种改进的性能,尽管你确实需要   使用F#3.1 FSharp.Core.dll运行时组件。

答案 3 :(得分:6)

编辑:此答案仅对简单格式字符串有效,例如“%s”或“%d”。请参阅以下评论。

值得注意的是,如果你可以制作一个curried函数并重用它,那么反射只会执行一次。样品:

let w = new System.IO.StringWriter() :> System.IO.TextWriter
let printer = fprintf w "%d"
let printer2 d = fprintf w "%d" d

let print1() = 
   for i = 1 to 100000 do
      printer 2
let print2() = 
   for i = 1 to 100000 do
      printer2 2
let time f = 
   let sw = System.Diagnostics.Stopwatch()
   sw.Start()
   f()
   printfn "%s" (sw.ElapsedMilliseconds.ToString())

time print1
time print2

print1在我的机器上需要48毫秒,而print2需要1158毫秒。

相关问题