Domanda

I have a program that I am trying to make faster, mostly for the sake of making it faster to learn more about Haskell. For comparison I have written the same program in C and have a 4x speed improvement. I expected faster from C, but that kind of difference makes me think I have something wrong.

So I have profiled the Haskell code and over 50% of the time is spent producing the formatted String for output. So just this section takes more than my entire C program. The function is similar to this:

display :: POSIXTime -> [(Int, Centi)] -> IO()
display t l = putStrLn $ t_str ++ " " ++ l_str
    where
        t_str = show . timeToTimeOfDay . unsafeCoerce $ (t `mod` posixDayLength)
        l_str = intercalate " " $ map displayPair l
        displayPair (a,b) = show a ++ " " ++ show b

Notes about the code: The unsafeCoerce is to convert NominalDiffTime to DiffTime which have the same type but this is faster than toRational . fromRational which I had been using.

Centi is defined in Data.Fixed and is a number with 2 decimal places

TimeOfDay is as you would expect just hours, minutes and seconds (stored with picosecond accuracy).

`mod` posixDayLength is so we just get the time of day ignoring which day it is (because that is all I care about ... it is from a timestamp and I know that it had to be today - I just care what time today!).

I have tried using ShowS (String -> String) to concatenate results and this is not significantly faster.

I have tried using Data.Text but that makes the code slower (presumably spends too much time packing strings).

I used to have the putStrLn in a separate function but it is faster here (less thunks built up? but why?).

Is there an easy way to improve output performance in Haskell that I'm missing?

È stato utile?

Soluzione

For producing output the highest performance can be found by avoiding String in favour of either a ByteString or Text. In order to build the output there is a special data type called Builder. There is a good description with examples in the [ByteString] hackage description.

The resulting code looks like this:

import Data.Monoid
display :: POSIXTime -> [(Int, Centi)] -> IO()
display t l = hPutBuilder stdout $ t_str <> space <> l_str
    where
        space = (byteString . pack) " "
        t_str = (byteString . pack . show . timeToTimeOfDay . unsafeCoerce) $ (t `mod` posixDayLength)
        l_str = foldr <> space $ map displayPair l
        displayPair (a,b) = intDec a <> space <> (byteString . pack . show) b

The builder data type builds up chunks that it will then concatenate in O(1) in to a buffer for the output. Unfortunately, not all types have a builder for them and only the base types. So for outputting the others the only solution is to pack the string ... or perhaps to write a function to create a builder (and add it to the library?).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top