[Shootout-list] haskell optimization trend
Jean-Philippe Bernardy
Jean-Philippe Bernardy <JeanPhilippe.bernardy@gmail.com>
Sat, 23 Oct 2004 20:15:06 +0200
Hello everyone,
I've checked the evolution of the haskell benchmark implementations,
and I have to say that I'm disapointed.
Let's take an example: word count.
It used to be:
-----------------------
import IO
main = do file <- getContents
putStrLn $ show (length $ lines file) ++ " " ++
show (length $ words file) ++ " " ++
show (length file)
-------------------
and it has become... (don't bother to go into the details)
----------------
import System.IO
import Data.Array.IO
import Data.Array.Base (unsafeRead)
import Data.Word
import List
isspace :: Char -> Bool
isspace ' ' = True
isspace '\n' = True
isspace '\t' = True
isspace _ = False
wc :: Handle -> IO (Int, Int, Int)
wc h = do
buf <- newArray_ (0, bufSize - 1) :: IO (IOUArray Int Word8)
let
wcLoop :: Int -> Int -> Int -> Int -> Int -> Int -> IO (Int, Int, Int)
wcLoop prevIsSpace nl nw nc i n
| prevIsSpace `seq` nl `seq` nw `seq` nc `seq` i `seq` n
`seq` False =
undefined
| i == n =
do n' <- hGetArray h buf bufSize
if n' == 0
then return (nl, nw, nc)
else wcLoop prevIsSpace nl nw nc 0 n'
| otherwise =
do c <- fmap (toEnum . fromEnum) (unsafeRead buf i)
-- Watch out, unsafeRead used here. This gives about
-- 1.6x times speedup.
let cIsSpace = oneIf (isspace c)
wcLoop
cIsSpace
(incIf nl (c == '\n'))
(incIf nw (prevIsSpace > cIsSpace))
(nc + 1)
(i + 1)
n
wcLoop 1 0 0 0 0 0
where
bufSize :: Int
bufSize = 4096
oneIf c = if c then 1 else 0
incIf n c = if c then n + 1 else n
main :: IO ()
main = do
(nl, nw, nc) <- wc stdin
putStrLn $ concat $ intersperse " " $ map show [nl, nw, nc]
--------------
The difference? I belive it's 100 times faster. Cool huh? Perhaps, yet
it doesn't do justice to the haskell language (and in turn to ghc).
Why? Because no sane programmer would ever consider writing the
optimized version! (except for the fun of it) It might be much faster
than the short one, but it is still much slower than C implementation,
requires MUCH more thought to write and read than the short one, and
is less modular.
What's the point of using a high level language if it is to emulate
low level techniques? The whole point is to leave the optimisation and
implementation details to the compiler.
Yet, you might argue that the purpose of the benchmark is to write the
most optimal program, whatever the cost. Fine, but the size (LOC) of
the implementations are benchmarked too... Leading to a conflict.
Thus, I propose to fork the haskell benchmarks as such:
1. Crazy hacker haskell
2. Academic haskell
This would allow to push the language to its limits in each direction,
without interference. An additional benefit is that it would point to
optimization opportunities in the compiler.
What do you think?
Cheers,
JP.