Q:

How can I improve performance of producing String for output in Haskell

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?

A:

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

