2012-05-06 9 views
8

Estoy aprendiendo F # y una cosa que me preocupa de este lenguaje es el rendimiento. He escrito un pequeño punto de referencia en el que comparo el F # idiomático con un código de estilo imperativo escrito en el mismo idioma, y ​​para mi sorpresa, la versión funcional sale mucho más rápido.Seq.map más rápido que un bucle for normal?

El índice de referencia se compone de:

  1. de lectura en un archivo de texto usando File.ReadAllLines
  2. invirtiendo el orden de los caracteres dentro de cada línea
  3. volver escribir el resultado en el mismo archivo usando File.WriteAllLines .

Aquí está el código:

open System 
open System.IO 
open System.Diagnostics 

let reverseString(str:string) = 
    new string(Array.rev(str.ToCharArray())) 

let CSharpStyle() = 
    let lines = File.ReadAllLines("text.txt") 
    for i in 0 .. lines.Length - 1 do 
     lines.[i] <- reverseString(lines.[i]) 

    File.WriteAllLines("text.txt", lines) 

let FSharpStyle() = 
    File.ReadAllLines("text.txt") 
    |> Seq.map reverseString 
    |> (fun lines -> File.WriteAllLines("text.txt", lines)) 

let benchmark func message = 
    // initial call for warm-up 
    func() 

    let sw = Stopwatch.StartNew() 
    for i in 0 .. 19 do 
     func() 

    printfn message sw.ElapsedMilliseconds 


[<EntryPoint>] 
let main args = 
    benchmark CSharpStyle "C# time: %d ms" 
    benchmark FSharpStyle "F# time: %d ms" 
    0 

Sea cual sea el tamaño del archivo, la versión "F # al estilo" se completa en alrededor del 75% del tiempo de la versión "C# de estilo". Mi pregunta es, ¿por qué es eso? No veo una ineficiencia obvia en la versión imperativa.

+1

Kudos @Dr_Asik para una pregunta bien preparada. –

Respuesta

10

Seq.map es diferente de Array.map. Debido a que las secuencias (IEnumerable<T>) no se evalúan hasta que se enumeran, en el código de estilo F # no se realiza ningún cálculo hasta File.WriteAllLines en bucles a través de la secuencia (no matriz) generada por Seq.map.

En otras palabras, su versión de estilo C# está invirtiendo todas las cadenas y almacenando las cadenas invertidas en una matriz, y luego recorriendo la matriz para escribir en el archivo. La versión de estilo F # está invirtiendo todas las cadenas y escribiéndolas más o menos directamente en el archivo. Eso significa que el código de estilo C# recorre todo el archivo tres veces (leer en matriz, construir matriz invertida, escribir matriz en archivo), mientras que el código de estilo F # recorre todo el archivo solo dos veces (leer en matriz, escribir líneas invertidas para archivar).

Te obtener el mejor rendimiento de todos si se ha utilizado en lugar de File.ReadLinesFile.ReadAllLines combinado con Seq.map - pero el archivo de salida tendría que ser diferente de su archivo de entrada, como era de estar escribiendo en la salida al mismo tiempo la lectura de entrada.

+1

Ah, ya lo veo: la versión C# llama a File.WriteAllLines (cadena, cadena []) mientras que la versión F # llama a File.WriteAllLines (cadena, IEnumerable ). Por lo tanto, en realidad solo hay 2 bucles en lugar de 3. No me vino a la mente que hubiera otras sobrecargas de ese método. ¡Gracias por la explicación! – Asik

1

El formulario Seq.map tiene varias ventajas sobre un bucle normal. Puede precomputar la referencia de función solo una vez; puede evitar las asignaciones variables; y puede usar la longitud de la secuencia de entrada para preseleccionar la matriz de resultados.

+1

Parece puntos muy válidos, pero tengo dificultades para ver lo que quieres decir. ¿Podría ampliar e ilustrar cada punto un poco? Gracias. – Asik

Cuestiones relacionadas