I am learning Haskell, and as a task for myself I was trying to implement a Universal Machine from ICFP Contest 2006. I came up with a code, which, at a first glance, seems to be working. However, when I try to execute any application for that Universal Machine provided on the contest's website (e.g. sandmark.umz), my implementation is too slow to actually run anything. Self-check did not finish in a couple of hours, and I had to kill the process. So, I am clearly doing something wrong, I just don't know what.
I have tried to use Haskell's profiler, but I couldn't make any sense out of those numbers as well. Garbage collection doesn't seem to be taking a lot of time (3 seconds out of 173 seconds of a sample). However, total allocated memory during those 173 seconds was almost 6 GB, while the maximum heap size was 13 MB.
Could you help me understand, what is wrong with my code? I know that the amount of code is quite large, but I am not sure how to come up with a minimum reproducible example in my case, when I don't really know what is relevant, and what is not. Thank you.
module Main where
import System.Environment (getArgs)
import System.Exit (exitSuccess)
import System.IO (hPutStrLn, stderr)
import System.IO.Error (catchIOError)
import Control.Monad (when)
import Control.Monad.Loops (iterateM_)
import Data.Array.IO (IOUArray, newArray, newListArray, readArray, writeArray, mapArray)
import Data.Bits
import Data.Binary.Get (getWord32be, runGet, isEmpty, Get)
import Data.Char (chr, ord)
import Data.Word (Word32)
import Data.Maybe (fromJust)
import qualified Data.IntMap.Strict as M
import qualified Data.ByteString.Lazy as B
import qualified Data.IntSet as IntSet
data UMState = UMState {
getFinger :: Word32,
getRegisters :: IOUArray Int Word32,
getArrays :: M.IntMap (IOUArray Word32 Word32),
getFreeIds :: [Int],
getMaxId :: Int,
getCopiedPlatters :: IntSet.IntSet
}
getOperation :: Word32 -> Int
getOperation x = fromIntegral $ (x `shiftR` 28) .&. 15
getRegisterIds :: Word32 -> (Int, Int, Int)
getRegisterIds x = (fromIntegral $ (x `shiftR` 6) .&. 7, fromIntegral $ (x `shiftR` 3) .&. 7, fromIntegral $ x .&. 7)
getOrthography :: Word32 -> (Int, Word32)
getOrthography x = (fromIntegral $ (x `shiftR` 25) .&. 7, x .&. 33554431)
setFinger :: UMState -> Word32 -> UMState
setFinger (UMState {
getFinger = _,
getRegisters = regs,
getArrays = arr,
getFreeIds = fids,
getMaxId = mid,
getCopiedPlatters = cp
}) f' = UMState {
getFinger = f',
getRegisters = regs,
getArrays = arr,
getFreeIds = fids,
getMaxId = mid,
getCopiedPlatters = cp
}
removePlatter :: UMState -> Int -> UMState
removePlatter (UMState {
getFinger = f,
getRegisters = regs,
getArrays = arr,
getFreeIds = fids,
getMaxId = mid,
getCopiedPlatters = cp
}) pid = UMState {
getFinger = f,
getRegisters = regs,
getArrays = M.delete pid arr,
getFreeIds = (pid:fids),
getMaxId = mid,
getCopiedPlatters = cp
}
insertPlatter :: UMState -> Int -> IOUArray Word32 Word32 -> UMState
insertPlatter (UMState {
getFinger = f,
getRegisters = regs,
getArrays = arr,
getFreeIds = fids@(hfid:tfids),
getMaxId = mid,
getCopiedPlatters = cp
}) pid platter = UMState {
getFinger = f,
getRegisters = regs,
getArrays = M.insert pid platter arr,
getFreeIds = if pid == hfid then tfids else fids,
getMaxId = max mid pid,
getCopiedPlatters = cp
}
insertPlatter (UMState {
getFinger = f,
getRegisters = regs,
getArrays = arr,
getFreeIds = [],
getMaxId = mid,
getCopiedPlatters = cp
}) pid platter = UMState {
getFinger = f,
getRegisters = regs,
getArrays = M.insert pid platter arr,
getFreeIds = [],
getMaxId = max mid pid,
getCopiedPlatters = cp
}
setCopiedPlatters :: UMState -> IntSet.IntSet -> UMState
setCopiedPlatters (UMState {
getFinger = f,
getRegisters = regs,
getArrays = arr,
getFreeIds = fids,
getMaxId = mid,
getCopiedPlatters = _
}) copied' = UMState {
getFinger = f,
getRegisters = regs,
getArrays = arr,
getFreeIds = fids,
getMaxId = mid,
getCopiedPlatters = copied'
}
main = do
args <- getArgs
fileName <- parseArgs $ filter (\arg -> arg /= "--") args
platters <- B.readFile fileName
array0 <- listToArray (runGet readPlatters platters)
regs <- (newArray (0, 7) 0 :: IO (IOUArray Int Word32))
let initState = (UMState {
getFinger = 0,
getRegisters = regs,
getArrays = M.insert 0 array0 M.empty,
getFreeIds = [],
getMaxId = 0,
getCopiedPlatters = IntSet.empty
})
in iterateM_ spinCycle initState
parseArgs :: [String] -> IO (String)
parseArgs [arg] = return arg
parseArgs args = fail $ "Exactly one argument expected. Found: " ++ (show args)
readPlatters :: Get [Word32]
readPlatters = do
empty <- isEmpty
if empty
then return []
else do
platter <- getWord32be
theRest <- readPlatters
return (platter:theRest)
listToArray :: [Word32] -> IO (IOUArray Word32 Word32)
listToArray lst = newListArray (fromIntegral 0, fromIntegral (length lst) - 1) lst
spinCycle :: UMState -> IO (UMState)
spinCycle state = do
platter <- readArray (fromJust (M.lookup 0 (getArrays state))) (getFinger state)
let state' = setFinger state $ getFinger state + 1
(aId, bId, cId) = getRegisterIds platter
regs = getRegisters state'
arrays = getArrays state' in (
case (getOperation platter) of
0 -> do
runConditionalMove aId bId cId regs
return state'
1 -> do
runArrayIndex aId bId cId regs arrays
return state'
2 -> runArrayAmendment aId bId cId state'
3 -> do
runAddition aId bId cId regs
return state'
4 -> do
runMultiplication aId bId cId regs
return state'
5 -> do
runDivision aId bId cId regs
return state'
6 -> do
runNand aId bId cId regs
return state'
7 -> runHalt
8 -> runAllocation bId cId state'
9 -> runAbandonment cId state'
10 -> do
runOutput cId regs
return state'
11 -> do
runInput cId regs
return state'
12 -> runLoadProgram bId cId state'
13 -> do
let (reg, val) = getOrthography platter
in (runOrthography reg val regs)
return state'
)
-- #0. Conditional Move.
runConditionalMove :: Int -> Int -> Int -> IOUArray Int Word32 -> IO ()
runConditionalMove a b c regs = do
hPutStrLn stderr ("conditionalMove " ++ (show a) ++ " " ++ (show b) ++ " " ++ (show c))
cRead <- readArray regs c
when (cRead /= 0) $ do
bRead <- readArray regs b
writeArray regs a bRead
-- #1. Array Index.
runArrayIndex :: Int -> Int -> Int -> IOUArray Int Word32 -> M.IntMap (IOUArray Word32 Word32) -> IO ()
runArrayIndex a b c regs arrays = do
hPutStrLn stderr ("arrayIndex " ++ (show a) ++ " " ++ (show b) ++ " " ++ (show c))
bRead <- readArray regs b
cRead <- readArray regs c
val <- readArray (fromJust (M.lookup (fromIntegral bRead) arrays)) cRead
writeArray regs a val
-- #2. Array Amendment.
runArrayAmendment :: Int -> Int -> Int -> UMState -> IO (UMState)
runArrayAmendment a b c state = do
hPutStrLn stderr ("arrayAmendment " ++ (show a) ++ " " ++ (show b) ++ " " ++ (show c))
aRead <- readArray (getRegisters state) a
bRead <- readArray (getRegisters state) b
cRead <- readArray (getRegisters state) c
stateToWrite <- if IntSet.member (fromIntegral aRead) (getCopiedPlatters state) then (do
pCopy <- mapArray id (fromJust (M.lookup (fromIntegral aRead) (getArrays state)))
let state' = insertPlatter state (fromIntegral aRead) pCopy
state'' = setCopiedPlatters state' $ IntSet.delete (fromIntegral aRead) (getCopiedPlatters state')
in return state''
) else return state
writeArray (fromJust (M.lookup (fromIntegral aRead) (getArrays stateToWrite))) bRead cRead
return stateToWrite
-- #3. Addition.
runAddition :: Int -> Int -> Int -> IOUArray Int Word32 -> IO ()
runAddition a b c regs = do
hPutStrLn stderr ("addition " ++ (show a) ++ " " ++ (show b) ++ " " ++ (show c))
bRead <- readArray regs b
cRead <- readArray regs c
writeArray regs a (bRead + cRead)
-- #4. Multiplication.
runMultiplication :: Int -> Int -> Int -> IOUArray Int Word32 -> IO ()
runMultiplication a b c regs = do
hPutStrLn stderr ("multiplication " ++ (show a) ++ " " ++ (show b) ++ " " ++ (show c))
bRead <- readArray regs b
cRead <- readArray regs c
writeArray regs a (bRead * cRead)
-- #5. Division.
runDivision :: Int -> Int -> Int -> IOUArray Int Word32 -> IO ()
runDivision a b c regs = do
hPutStrLn stderr ("division " ++ (show a) ++ " " ++ (show b) ++ " " ++ (show c))
bRead <- readArray regs b
cRead <- readArray regs c
writeArray regs a (bRead `div` cRead)
-- #6. Not-And.
runNand :: Int -> Int -> Int -> IOUArray Int Word32 -> IO ()
runNand a b c regs = do
hPutStrLn stderr ("nand " ++ (show a) ++ " " ++ (show b) ++ " " ++ (show c))
bRead <- readArray regs b
cRead <- readArray regs c
writeArray regs a (complement $ bRead .&. cRead)
-- #7. Halt.
runHalt = exitSuccess :: IO (UMState)
-- #8. Allocation.
runAllocation :: Int -> Int -> UMState -> IO (UMState)
runAllocation b c state = do
hPutStrLn stderr ("allocation " ++ (show b) ++ " " ++ (show c))
cRead <- readArray (getRegisters state) c
pArray <- (newArray (0, cRead) 0 :: IO (IOUArray Word32 Word32))
(state', newId) <-
case (getFreeIds state) of
(freeId:_) -> return (insertPlatter state freeId pArray, freeId)
[] -> let maxId' = getMaxId state + 1 in return (insertPlatter state maxId' pArray, maxId')
writeArray (getRegisters state') b (fromIntegral newId)
return state'
-- #9. Abandonment.
runAbandonment :: Int -> UMState -> IO (UMState)
runAbandonment c state = do
hPutStrLn stderr ("abandonment " ++ (show c))
cRead <- readArray (getRegisters state) c
return (removePlatter state $ fromIntegral cRead)
-- #10. Output.
runOutput :: Int -> IOUArray Int Word32 -> IO ()
runOutput c regs = do
cRead <- readArray regs c
when (cRead < 256) $ putChar . chr . fromIntegral $ cRead
-- #11. Input.
runInput :: Int -> IOUArray Int Word32 -> IO ()
runInput c regs = do
cRead <- getChar `catchIOError` (\_ -> return $ chr 255)
writeArray regs c (fromIntegral $ ord cRead)
-- #12. Load Program.
runLoadProgram :: Int -> Int -> UMState -> IO (UMState)
runLoadProgram b c state = do
hPutStrLn stderr ("loadProgram " ++ (show b) ++ " " ++ (show c))
bRead <- readArray (getRegisters state) b
cRead <- readArray (getRegisters state) c
let bReadInt = fromIntegral bRead
pCopy = fromJust (M.lookup bReadInt (getArrays state))
copied = IntSet.insert 0 (getCopiedPlatters state)
copied' = IntSet.insert bReadInt copied
state' = insertPlatter state 0 pCopy
state'' = setFinger state' cRead
state''' = setCopiedPlatters state'' copied'
in return state'''
-- #13. Orthography.
runOrthography :: Int -> Word32 -> IOUArray Int Word32 -> IO ()
runOrthography reg val regs = writeArray regs reg val