Performance of lazy and strict evaluations in Haskell - haskell

I implemented the Winograd algorithm on Haskell and, trying to improve it, decided to make it more strict. I did this, but I do not understand why it started to work faster. Yesterday I asked a similar question, but posted not quite correct code.
Since that code does not show a problem, I post the code completely.
module Main where
import qualified Data.Vector as V
import qualified Data.Matrix as M
import Control.DeepSeq
import Control.Exception
import System.Clock
import System.Mem
import Data.Time
matrixCtor :: Int -> Int -> Int -> M.Matrix Int
matrixCtor x y size = M.matrix size size $ \(i,j) -> x*i+y*j
winogradLazy :: M.Matrix Int -> M.Matrix Int -> M.Matrix Int
winogradLazy a b = c
where
n = M.nrows a
p = M.ncols a
m = M.ncols b
-- Translate into vectors, since indexing in matrices takes longer.
-- Matrix b is also transposed
a' = V.generate n $ \i -> M.getRow (i+1) a
bt' = V.generate m $ \j -> M.getCol (j+1) b
rows = V.generate n $ \i -> group $ V.unsafeIndex a' i
cols = V.generate m $ \j -> group $ V.unsafeIndex bt' j
group x = foldl (groupHelper x) 0 [0,2..p-1]
groupHelper x acc i = let
x1 = V.unsafeIndex x (i)
x2 = V.unsafeIndex x (i+1)
in
acc - x1 * x2
c = M.matrix n m $ \(i,j) ->
let
a = V.unsafeIndex rows (i-1) + V.unsafeIndex cols (j-1)
b = wsum (V.unsafeIndex a' (i-1)) (V.unsafeIndex bt' (j-1))
in
a + b
wsum r c = foldl (wsumHelper r c) 0 [0,2..p-1]
wsumHelper r c acc i = let
x1 = V.unsafeIndex r (i)
x2 = V.unsafeIndex r (i+1)
y1 = V.unsafeIndex c (i)
y2 = V.unsafeIndex c (i+1)
in
acc +(x1+y2)*(x2+y1)
winogradStrict :: M.Matrix Int -> M.Matrix Int -> M.Matrix Int
winogradStrict a b = c
where
n = M.nrows a
p = M.ncols a
m = M.ncols b
-- Translate into vectors, since indexing in matrices takes longer.
-- Matrix b is also transposed
a' = V.generate n $ \i -> M.getRow (i+1) a
bt' = V.generate m $ \j -> M.getCol (j+1) b
rows = V.generate n $ \i -> group $ V.unsafeIndex a' i
cols = V.generate m $ \j -> group $ V.unsafeIndex bt' j
group x = foldl (groupHelper x) 0 [0,2..p-1]
groupHelper x acc i = let
x1 = V.unsafeIndex x (i)
x2 = V.unsafeIndex x (i+1)
in
acc - x1 * x2
c = a' `deepseq` bt' `deepseq` M.matrix n m $ \(i,j) ->
let
a = V.unsafeIndex rows (i-1) + V.unsafeIndex cols (j-1)
b = wsum (V.unsafeIndex a' (i-1)) (V.unsafeIndex bt' (j-1))
in
a + b
wsum r c = foldl (wsumHelper r c) 0 [0,2..p-1]
wsumHelper r c acc i = let
x1 = V.unsafeIndex r (i)
x2 = V.unsafeIndex r (i+1)
y1 = V.unsafeIndex c (i)
y2 = V.unsafeIndex c (i+1)
in
acc + (x1+y2)*(x2+y1)
lazyTest :: IO ()
lazyTest = do
let a = matrixCtor 2 (-1) 500
let b = matrixCtor 2 (-3) 500
evaluate $ force a
evaluate $ force b
start <- getCurrentTime
let c = winogradLazy a b
evaluate $ force c
end <- getCurrentTime
print (diffUTCTime end start)
strictTest :: IO ()
strictTest = do
let a = matrixCtor 2 (-1) 500
let b = matrixCtor 2 (-3) 500
evaluate $ force a
evaluate $ force b
start <- getCurrentTime
let c = winogradStrict a b
evaluate $ force c
end <- getCurrentTime
print (diffUTCTime end start)
main :: IO ()
main = do
performMajorGC
lazyTest
performMajorGC
strictTest
in the strict version before computing the matrix c, I do the following:
a' `deepseq` bt' `deepseq`
and because of this I get the following results
2.083201s --lazyTest
0.613508s --strictTest

Related

Haskell time measurment

I have the following matrix multiplication function
winograd :: M.Matrix Int -> M.Matrix Int -> M.Matrix Int
winograd a b = c
where
ra = M.nrows a
ca = M.ncols a
rb = M.nrows b
cb = M.ncols b
isEven = even ca
avs = V.generate ra $ \i -> M.getRow (i+1) a
bvs = V.generate cb $ \j -> M.getCol (j+1) b
rows = V.generate ra $ \i -> wgdGroup $ V.unsafeIndex avs i
cols = V.generate cb $ \j -> wgdGroup $ V.unsafeIndex bvs j
wgdGroup x = let
finish = (V.length x - 1)
in
forLoopFold 0 (<finish) (+2) 0 $
\acc i -> acc
- V.unsafeIndex x i * V.unsafeIndex x (i+1)
c = if isEven then
M.matrix ra cb $
\(i,j) ->
V.unsafeIndex rows (i-1) +
V.unsafeIndex cols (j-1) +
g (V.unsafeIndex avs (i-1)) (V.unsafeIndex bvs (j-1))
else
M.matrix ra cb $
\(i,j) ->
V.unsafeIndex rows (i-1) +
V.unsafeIndex cols (j-1) +
g (V.unsafeIndex avs (i-1)) (V.unsafeIndex bvs (j-1)) +
V.last (V.unsafeIndex avs (i-1)) * V.last (V.unsafeIndex bvs (j-1))
g r c = forLoopFold 0 (<(ca-1)) (+2) 0 $ \acc i ->
let
x1 = V.unsafeIndex r i
x2 = V.unsafeIndex r (i+1)
y1 = V.unsafeIndex c i
y2 = V.unsafeIndex c (i+1)
in
acc + (x1+y2)*(x2+y1)
And I measure the execution time of this function as follows, using clock and formatting packages (yes, I know about Criterion, but now I need to measure it this way):
main :: IO ()
main = do
let a = simple 2 (-1) 1000
let b = simple 2 (-3) 1000
start <- getTime Realtime
let
c = winograd a b
end <- c `deepseq` getTime Realtime
fprint (timeSpecs % "\n") start end
where simple is
simple :: Int -> Int -> Int -> M.Matrix Int
simple x y size = M.matrix size size $ \(i,j) -> x*i+y*j
I get the result about 5 seconds. But when I get rid of the function simple and do like this:
main :: IO ()
main = do
let a = M.matrix 1000 1000 $ \(i,j) -> 2*i-1*j
let b = M.matrix 1000 1000 $ \(i,j) -> 2*i-3*j
start <- getTime Realtime
let
c = winograd a b
end <- c `deepseq` getTime Realtime
fprint (timeSpecs % "\n") start end
the time increases to 15 seconds!
Very interesting why. Compiled with flag -O2.

SHA-1 in haskell producing wrong hashes

I wrote a program to perform SHA-1 in haskell, and while it does produce hashes, they do not match with the ones produced by other SHA-1 programs
Example: cat
hashes to: b5be86bc8bccfc24b01b093228ebb96fc92fa804 but is supposed to hash to 9d989e8d27dc9e0ec3389fc855f142c3d40f0c50
My code is:
(old code omitted)
I have no idea what is wrong. Can someone tell me where I made a mistake?
Edit:
I fixed the stuff that was pointed out, however it is still not working. It works correctly up until the inner loop.
I cleaned up the code so the functions for the inner loop are available as f1, f2 and f3
cat now interestingly hashes to ebe6c9fa1afa0ef5a0ca80bab251fd41cc29127e.
Code:
import Data.Word
import Data.Bits
import Data.Char (ord, intToDigit)
import Data.Binary (encode, decode)
import Numeric (showHex, showIntAtBase)
import System.IO (stdin)
import Data.Sequence ((<|), (|>))
import qualified Data.Sequence as S
import qualified Data.ByteString.Lazy as B
type Quintuple32 = (Word32, Word32, Word32, Word32, Word32)
addQuintuple (a, b, c, d, e) (f, g, h, i, j) =
(a + f, b + g, c + h, d + i, e + j)
shower :: Quintuple32 -> String
shower (a, b, c, d, e) = concatMap (`showHex` "") [a, b, c, d, e]
hash :: Int -> S.Seq Word32 -> Quintuple32 -> Quintuple32
hash i w h#(a, b, c, d, e)
| i < 20 = hash (i + 1) w (newhash (f1 h + k1))
| i < 40 = hash (i + 1) w (newhash (f2 h + k2))
| i < 60 = hash (i + 1) w (newhash (f3 h + k3))
| i < 80 = hash (i + 1) w (newhash (f2 h + k4))
| otherwise = h
where (k1, k2, k3, k4) = (0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6)
newhash a' = (rotate a 5 + a' + e + (w `S.index` i), a, rotate b 30, c, d)
f1 :: Quintuple32 -> Word32
f1 (_, b, c, _, _) = (b .&. c) .|. (complement b .&. c)
f2 :: Quintuple32 -> Word32
f2 (_, b, c, d, _) = b `xor` c `xor` d
f3 :: Quintuple32 -> Word32
f3 (_, b, c, d, _) = (b .&. c) .|. (b .&. d) .|. (c .&. d)
starting :: Quintuple32
starting = (0x67452301
, 0xEFCDAB89
, 0x98BADCFE
, 0x10325476
, 0xC3D2E1F0)
hasher :: Quintuple32 -> S.Seq Word32 -> Quintuple32
hasher acc x = addQuintuple acc (hash 0 (extend x) acc)
process :: B.ByteString -> Quintuple32
process = foldl hasher starting . chunks . pad
extend :: S.Seq Word32 -> S.Seq Word32
extend = extend' 16
extend' :: Int -> S.Seq Word32 -> S.Seq Word32
extend' 80 a = a
extend' i a = extend' (i + 1) (a |> xored)
where xored = rotate ((a `S.index` (i - 3)) `xor`
(a `S.index` (i - 8)) `xor`
(a `S.index` (i - 14)) `xor`
(a `S.index` (i - 16))) 1
toBytes :: String -> B.ByteString
toBytes = B.pack . map (fromIntegral . ord)
splitEvery n xs
| B.null xs = S.empty
| otherwise = B.take n xs <| splitEvery n (B.drop n xs)
chunks :: B.ByteString -> [S.Seq Word32]
chunks xs
| B.null xs = []
| otherwise = x : chunks (B.drop 64 xs)
where x = fmap decode (splitEvery 4 (B.take 64 xs))
pad :: B.ByteString -> B.ByteString
pad xs = B.append (add0 $ add1 xs) length64
where length64 = encode (fromIntegral (8 * B.length xs) :: Word64)
add1 :: B.ByteString -> B.ByteString
add1 = flip B.append (B.singleton 128)
add0 :: B.ByteString -> B.ByteString
add0 xs
| modulo /= 448 = add0 $ B.append xs (B.singleton 0)
| otherwise = xs
where modulo = (B.length xs * 8) `rem` 512
Also, a small question: is something like (a, b) = (8, 9) an acceptable thing to do to set multiple variables?
Oh, another one of these!
Two errors jump out at me immediately:
pad :: B.ByteString -> B.ByteString
pad xs = B.append (add0 $ add1 xs) length64
where length64 = encode (fromIntegral (B.length xs) :: Word64)
Notice the length you append is supposed to be the bit length, not the byte length.
add1 :: B.ByteString -> B.ByteString
add1 = flip B.append (B.singleton 255)
Notice 255 /= 0b10000000 and the pad is supposed to be the later.
In general you debug these by 1) going over the spec again and again. 2) Comparing to another implementation, such as Adam Wick's SHA package, and comparing for equality at as fine grained level as possible.
EDIT: There are two more bugs, basically transcription errors. Look around a bit and shout if you're still stuck.

Tail-recursive function consuming memory

I have a clearly tail-recursive function for finding (choose n k) mod 10007 (with k nonnegative)
Why is this function consuming lots of memory for large inputs? (ie 100000000 choose 50000000) I can understand if it might be slow, but it shouldn't use more than constant memory, should it? (assuming GHC knows about tail-call optimization)
GHC version 7.8.3
modulus :: Int
modulus = 10007
choose :: Int -> Int -> Int
choose n1 k1
| s1 > 0 = 0
| otherwise = q1
where
(q1, s1) = doChoose n1 k1 (1, 0)
doChoose :: Int -> Int -> (Int, Int) -> (Int, Int)
doChoose _ 0 (qr, sr) = (qr, sr)
doChoose n k (qr, sr) =
doChoose (n `seq` (n-1)) (k-1) (qr `seq` (qn * qr `rem` modulus * inv qk `rem` modulus), sr `seq` (sn + sr - sk))
where
(qn, sn) = removePs n
(qk, sk) = removePs k
removePs :: Int -> (Int, Int)
removePs n =
case r of
0 -> (q0, s0 + 1)
_ -> (n, 0)
where
(q, r) = n `quotRem` modulus
(q0, s0) = removePs q
inv :: Int -> Int
inv = doInv 0 1 modulus . (`mod` modulus)
where
doInv x _ 1 0
| x < 0 = x + modulus
| otherwise = x
doInv _ _ _ 0 = error "Not relatively prime"
doInv x y a b = doInv y (x - q * y) b r
where
(q, r) = a `quotRem` b
I was putting the seq in the wrong place.
It needs to be:
n `seq` qr `seq` sr `seq` doChoose (n-1) (k-1) (qn * qr `rem` modulus * inv qk `rem` modulus, sn + sr - sk)
Otherwise the call to seq isn't evaluated until reaching the base-case and a chain of thunks is still built up.
This isn't strictly tail-recursive, but rather it's "mutually" tail-recursive since seq ultimately returns its second argument without modifying it.
By the way, to simplify your expressions, you can write a helper function:
force x = x `seq` x
or use force (no pun intended) from the Deepseq package. Then
doChoose (force n - 1) (k - 1) (qn * force qr * etc.)

New scope in 'do' notation

I'm trying to write a recursive function that mutates a Data.Vector.Unboxed.Mutable 'Vector', though the question applies to any monadic code, I think.
As a contrived example:
import Data.Vector.Unboxed as U
import Data.Vector.Unboxed.Mutable as M
import Control.Monad
import Control.Monad.ST
import Control.Monad.Primitive
f :: U.Vector Int -> U.Vector Int
f x = runST $ do
y <- U.thaw x
add1 y 0
U.freeze y
add1 :: (PrimMonad m) => MVector (PrimState m) Int -> Int -> m()
add1 v i | i == M.length v = return ()
add1 v i = do
c <- M.unsafeRead v i
M.unsafeWrite v i (c + 1)
add1 v (i+1)
However, v does not change in each recursive call. I would like to be able to remove v as a parameter to the function and inline 'add1' into f, but I need 'y' to be in scope.
I can get one step closer is by changing add1 (and keeping f the same) so that v is not passed in the recursion:
add1 :: (PrimMonad m) => MVector (PrimState m) Int -> m()
add1 v = do add1_ 0
where len = M.length v
add1_ i | i == len = do return ()
add1_ i = do
x <- M.unsafeRead v i
M.unsafeWrite v i (x + 1)
add1_ (i+1)
What I would really like is to totally inline add1 though. Here's a solution that doesn't quite compile yet:
f x = let len = U.length x
y = U.thaw x
add1 i | i == len = return ()
add1 i = do
y' <- y
c <- M.unsafeRead y' i
M.unsafeWrite y' i (c+1)
add1 (i+1)
in runST $ do
add1 0
y' <- y
U.freeze y'
GHC errors:
couldn't match type 'm0' with 'ST s'
couldn't match type 's' with 'PrimState m0'
Errors aside, this isn't optimal yet: I don't want to have to do (y' <- y) in every do statement (especially when add1 is recursive). I'd really like y' (the 'non-monadic' version of y) to just be in scope. Is there any way to do this?
(I apologize if I am horribly misusing monads in some way)
How about this?
f :: U.Vector Int -> U.Vector Int
f x = runST $ do
y <- U.thaw x
let add1 i | i == length x = return ()
| otherwise = do
c <- M.unsafeRead y i
M.unsafeWrite y i (c+1)
add1 (i+1)
add1 0
U.freeze y

How to use MonadRandom?

Can someone provide "for-dummies" example of how to use `MonadRandom'?
Currently I have code that does stuff like passing around the generator variable, all the way from the main function:
main = do
g <- getStdGen
r <- myFunc g
putStrLn "Result is : " ++ show r
--my complicated func
myFunc g x y z = afunc g x y z
afunc g x y z = bfunc g x y
bfunc g x y = cfunc g x
cfunc g x = ret where
(ret, _ ) = randomR (0.0, 1.0) g
Thanks
Basically all the extra g parameters can just be dropped. You then get random numbers using the functions from Control.Monad.Random (such as getRandomR). Here is your example (I added some args to make it compile):
import Control.Monad.Random
main = do
g <- getStdGen
let r = evalRand (myFunc 1 2 3) g :: Double
-- or use runRand if you want to do more random stuff:
-- let (r,g') = runRand (myFunc 1 2 3) g :: (Double,StdGen)
putStrLn $ "Result is : " ++ show r
--my complicated func
myFunc x y z = afunc x y z
afunc x y z = bfunc x y
bfunc x y = cfunc x
cfunc x = do
ret <- getRandomR (0.0,1.0)
return ret
You just run something in the RandT monad transformer with runRandT or evalRandT, and for the pure Rand monad, with runRand or evalRand:
main = do
g <- getStdGen
r = evalRand twoEliteNumbers g
putStrLn $ "Result is: " ++ show r
twoEliteNumbers :: (RandomGen g) => Rand g (Double, Double)
twoEliteNumbers = do
-- You can call other functions in the Rand monad
number1 <- eliteNumber
number2 <- eliteNumber
return $ (number1, number2)
eliteNumber :: (RandomGen g) => Rand g Double
eliteNumber = do
-- When you need random numbers, just call the getRandom* functions
randomNumber <- getRandomR (0.0, 1.0)
return $ randomNumber * 1337

Resources