Question

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?

Was it helpful?

Solution

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?).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top