Skip to content
Advertisement

Haskell extremely slow simple recurrence

I’m experimenting with Haskell profiling with a simple recursive max algorithm:

max_tag :: Integer -> [Integer] -> Integer
max_tag head [] = head
max_tag head (x:xs) =
    let {m = max_tag x xs} in
      let {b = (Prelude.<=) m head} in
        case b of {True -> head; False -> m}

When I compare it to a python imperative equivalent, I get a 10x speed factor in favor of python:

with open("input.txt") as fl:
    data = [int(d) for d in fl.read().splitlines()]
max_ = 0
for d in data:
    if max_ < d:
        max_ = d
print(max_)
  • It seems there is an inherent limitation of using tail recursion in the Haskell case, am I right?
  • Any other way to make the Haskell code faster?
  • The input file contains 1M unsigned, unbounded integers (on average 32 digits)

For completeness, here is the complete Haskell file (not sure it is needed):

import Max
import System.IO  
import Control.Monad
import System.Environment
import Prelude

readInt :: String -> Integer
readInt = read

max_tag :: Integer -> [Integer] -> Integer
max_tag head [] = head
max_tag head (x:xs) =
    let {m = max_tag x xs} in
      let {b = (Prelude.<=) m head} in
        case b of {True -> head; False -> m}

main = do
    args <- getArgs
    contents <- readFile "input.txt"
    let numbers_as_strings = words $ contents
    let numbers = map readInt numbers_as_strings
    let max_number = max_tag 0 numbers
    print max_number

EDIT: refactor suggested by @Willem Van Onsem, works ! (28 sec -> 12 sec)

max_bar :: Integer -> [Integer] -> Integer
max_bar head [] = head
max_bar head (x:xs) =
  let {b = head < x} in
    let {m = case b of {True -> x; False -> head}} in
      max_bar m xs

Any ideas on further improvements? I must be faster than python !

Advertisement

Answer

Using this to benchmark your function should make it go much faster (also be sure to use text version 2.0 or later):

import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Data.Text.Read as T

readInt :: T.Text -> Integer
readInt t = 
  case T.signed T.decimal t of
    Right (x, _) -> x

main :: IO ()
main = do
    args <- getArgs
    contents <- T.readFile "input.txt"
    let numbers_as_strings = T.words contents
    let numbers = map readInt numbers_as_strings
    let max_number = max_tag 0 numbers
    print max_number

You can also speed up your max_tag function itself by making it tail recursive and thus avoiding a bunch of memory allocations:

max_tag :: Integer -> [Integer] -> Integer
max_tag head [] = head
max_tag head (x:xs) 
  | x <= head = max_tag head xs
  | otherwise = max_tag x xs

You can make it even slightly faster by using foldr so that you get foldr/build fusion with map readInt:

max_tag :: Integer -> [Integer] -> Integer
max_tag x0 xs = foldr (x go y -> if x > y then go x else go y) id xs x0
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement