performance - tuplas - tipo de funciones haskell
¿Por qué la versión F#de este programa es 6 veces más rápida que la de Haskell? (3)
Si cambia a
ByteString
y se
ByteString
con listas simples de Haskell (en lugar de vectores), obtendrá una solución más eficiente.
También puede reescribir la función de resolución con
un solo pliegue izquierdo
y omitir el zip y el escaneo derecho
(1)
.
En general, en mi máquina, obtengo una mejora de rendimiento 20 veces mayor en comparación con su solución
Haskell
(2)
.
El siguiente código de Haskell funciona más rápido que el código F #:
import Data.List (unfoldr)
import Control.Applicative ((<$>))
import Control.Monad (replicateM_)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C
parse :: ByteString -> [Int]
parse = unfoldr $ C.readInt . C.dropWhile (== '' '')
solve :: [Int] -> Int
solve xs = foldl go (const 0) xs minBound
where go f x s = if s < x then f x else s - x + f s
main = do
[n] <- parse <$> B.getLine
replicateM_ n $ B.getLine >> B.getLine >>= print . solve . parse
1. Vea las
edits
para una versión anterior de esta respuesta que implementa
solve
usando
zip
y
scanr
.
2. El sitio web de HackerRank muestra una mejora aún mayor en el rendimiento.
Versión de Haskell (1.03s):
module Main where
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Control.Monad
import Control.Applicative ((<$>))
import Data.Vector.Unboxed (Vector,(!))
import qualified Data.Vector.Unboxed as V
solve :: Vector Int -> Int
solve ar =
V.foldl'' go 0 ar'' where
ar'' = V.zip ar (V.postscanr'' max 0 ar)
go sr (p,m) = sr + m - p
main = do
t <- fmap (read . T.unpack) TIO.getLine -- With Data.Text, the example finishes 15% faster.
T.unlines . map (T.pack . show . solve . V.fromList . map (read . T.unpack) . T.words)
<$> replicateM t (TIO.getLine >> TIO.getLine) >>= TIO.putStr
Versión F # (0.17s):
open System
let solve (ar : uint64[]) =
let ar'' =
let t = Array.scanBack max ar 0UL |> fun x -> Array.take (x.Length-1) x
Array.zip ar t
let go sr (p,m) = sr + m - p
Array.fold go 0UL ar''
let getIntLine() =
Console.In.ReadLine().Split [|'' ''|]
|> Array.choose (fun x -> if x <> "" then uint64 x |> Some else None)
let getInt() = getIntLine().[0]
let t = getInt()
for i=1 to int t do
getInt() |> ignore
let ar = getIntLine()
printfn "%i" (solve ar)
Los dos programas anteriores son las soluciones para el
problema Stock Maximize
y los tiempos son para el primer caso de prueba del botón
Run Code
.
Por alguna razón, la versión F # es aproximadamente 6 veces más rápida, pero estoy bastante seguro de que si reemplazara las funciones lentas de la biblioteca con bucles imperativos, podría acelerarla al menos 3 veces y más probablemente 10 veces.
¿Podría la versión de Haskell ser mejorada de manera similar?
Estoy haciendo lo anterior con fines de aprendizaje y, en general, me resulta difícil descubrir cómo escribir un código Haskell eficiente.
Si quisiera hacerlo rápidamente en F #, evitaría todas las funciones de orden superior dentro de
solve
y simplemente escribiría un bucle imperativo de estilo C:
let solve (ar : uint64[]) =
let mutable sr, m = 0UL, 0UL
for i in ar.Length-1 .. -1 .. 0 do
let p = ar.[i]
m <- max p m
sr <- sr + m - p
sr
Según mis mediciones, esto es 11 veces más rápido que su F #.
Entonces, el rendimiento está limitado por la capa IO (análisis unicode) y la división de cadenas. Esto se puede optimizar leyendo en un búfer de bytes y escribiendo el lexer a mano:
let buf = Array.create 65536 0uy
let mutable idx = 0
let mutable length = 0
do
use stream = System.Console.OpenStandardInput()
let rec read m =
let c =
if idx < length then
idx <- idx + 1
else
length <- stream.Read(buf, 0, buf.Length)
idx <- 1
buf.[idx-1]
if length > 0 && ''0''B <= c && c <= ''9''B then
read (10UL * m + uint64(c - ''0''B))
else
m
let read() = read 0UL
for _ in 1UL .. read() do
Array.init (read() |> int) (fun _ -> read())
|> solve
|> System.Console.WriteLine
Solo para el registro, la versión F # tampoco es óptima. No creo que realmente importe en este momento, pero si la gente quisiera comparar el rendimiento, vale la pena señalar que se puede hacer más rápido.
No lo he intentado mucho (ciertamente puedes hacerlo aún más rápido usando una mutación restringida, que no estaría en contra de la naturaleza de F #), sino un simple cambio para usar
Seq
lugar de
Array
en los lugares correctos (para evitar asignar matrices temporales) hace que el código sea de 2x a 3x más rápido:
let solve (ar : uint64[]) =
let ar'' = Seq.zip ar (Array.scanBack max ar 0UL)
let go sr (p,m) = sr + m - p
Seq.fold go 0UL ar''
Si usa
Seq.zip
, también puede descartar la llamada de
Seq.zip
(porque
Seq.zip
trunca la secuencia automáticamente).
Medido usando
#time
usando el siguiente fragmento:
let rnd = Random()
let inp = Array.init 100000 (fun _ -> uint64 (rnd.Next()))
for a in 0 .. 10 do ignore (solve inp) // Measure this line
Obtuve alrededor de 150ms para el código original y algo entre 50-75ms usando la nueva versión.