Skip to content

Commit 3a6928f

Browse files
author
Jaro Reinders
committed
Even nicer lifted code
1 parent c341dd1 commit 3a6928f

File tree

2 files changed

+130
-103
lines changed

2 files changed

+130
-103
lines changed

fleet-array.cabal

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ author: Jaro Reinders
77
maintainer: [email protected]
88
build-type: Simple
99
category: Data, Data Structures, Array
10-
synopsis: Fleet arrays are pure, but support fast updates in single-threaded use
10+
synopsis: Fleet arrays are pure, but support fast updates if used linearly
1111
tested-with: GHC ==9.10.1 || ==9.8.4 || ==9.6.6
1212
description:
1313
Updating a pure array in Haskell usually requires copying the whole array,
@@ -19,6 +19,9 @@ description:
1919
performance overhead. We hope this package can form the basis of a pure
2020
and performant array library.
2121

22+
Fleet arrays can be more than 10x faster than 'IntMap' if used densly and
23+
linearly.
24+
2225
common warnings
2326
ghc-options: -Wall
2427

src/Fleet/Array.hs

Lines changed: 126 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{-# LANGUAGE MagicHash, UnboxedTuples, UnliftedDatatypes #-}
2-
{-# OPTIONS_GHC -Wno-name-shadowing -ddump-simpl -ddump-to-file -dsuppress-all -dno-suppress-type-signatures -dno-typeable-binds #-}
2+
{-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-all -dno-suppress-type-signatures -dno-typeable-binds #-}
3+
-- O2 is necessary to get the right call pattern specializations and remove all the lifted abstractions
4+
{-# OPTIONS_GHC -O2 #-}
35
{-# LANGUAGE LambdaCase #-}
46

57
{-|
@@ -25,16 +27,26 @@ import GHC.Exts hiding (fromList, toList, Lifted)
2527

2628
import Data.Kind (Type)
2729
import GHC.IO.Unsafe (unsafeDupablePerformIO)
28-
import GHC.Base (IO(IO), unIO)
30+
import GHC.Base (IO(IO))
2931

30-
data Op a = Set Int# a | Swap Int# Int#
32+
data Op a = Set {-# UNPACK #-} !Int a | Swap {-# UNPACK #-} !Int {-# UNPACK #-} !Int
3133

3234
-- | Fleet arrays.
33-
data Array a = DA (MutVar# RealWorld (ArrayData a))
34-
type ArrayData :: Type -> UnliftedType
35-
data ArrayData a
36-
= Current (MutableArray# RealWorld a)
37-
| Diff {-# UNPACK #-} !(Op a) (MutVar# RealWorld (ArrayData a))
35+
data Array a = A {-# UNPACK #-} !(ArrayVar a)
36+
type ArrayData# :: Type -> UnliftedType
37+
data ArrayData# a
38+
= Current# {-# UNPACK #-} !(MutArray a)
39+
| Diff# {-# UNPACK #-} !(Op a) {-# UNPACK #-} !(ArrayVar a)
40+
41+
data ArrayData a = Current (MutArray a) | Diff !(Op a) !(ArrayVar a)
42+
43+
to# :: ArrayData a -> ArrayData# a
44+
to# (Current x) = Current# x
45+
to# (Diff op v) = Diff# op v
46+
47+
from# :: ArrayData# a -> ArrayData a
48+
from# (Current# x) = Current x
49+
from# (Diff# op v) = Diff op v
3850

3951
instance Show a => Show (Array a) where
4052
show xs = "fromList " ++ show (toList xs)
@@ -43,150 +55,162 @@ instance Show a => Show (Array a) where
4355
aseq :: a -> b -> b
4456
aseq x y = x `seq` lazy y
4557

46-
type Lifted :: UnliftedType -> Type
47-
data Lifted a = Lifted a
58+
-- ArrayVar
59+
data ArrayVar a = AV (MutVar# RealWorld (ArrayData# a))
60+
newArrayVar :: ArrayData a -> IO (ArrayVar a)
61+
newArrayVar x = IO $ \s ->
62+
case newMutVar# (to# x) s of
63+
(# s', v #) -> (# s', AV v #)
4864

49-
{-# INLINE newMutVarIO #-}
50-
newMutVarIO :: forall (a :: UnliftedType). a -> IO (Lifted (MutVar# RealWorld a))
51-
newMutVarIO x = IO $ \s ->
52-
case newMutVar# x s of
53-
(# s', v #) -> (# s', Lifted v #)
65+
readArrayVar :: ArrayVar a -> IO (ArrayData a)
66+
readArrayVar (AV v) = IO $ \s -> case readMutVar# v s of (# s', x #) -> (# s', from# x #)
5467

55-
{-# INLINE readMutVarIO #-}
56-
readMutVarIO :: forall (a :: UnliftedType) b. MutVar# RealWorld a -> (a -> IO b) -> IO b
57-
readMutVarIO v f = IO (\s -> case readMutVar# v s of (# s', x #) -> unIO (f x) s')
68+
writeArrayVar :: ArrayVar a -> ArrayData a -> IO ()
69+
writeArrayVar (AV v) x = IO $ \s -> (# writeMutVar# v (to# x) s, () #)
5870

59-
{-# INLINE writeMutVarIO #-}
60-
writeMutVarIO :: forall (a :: UnliftedType). MutVar# RealWorld a -> a -> IO ()
61-
writeMutVarIO v x = IO (\s -> (# writeMutVar# v x s, () #))
71+
-- MutArray
6272

63-
readArrayIO :: MutableArray# RealWorld a -> Int# -> IO a
64-
readArrayIO arr i = IO (readArray# arr i)
73+
data MutArray a = MA (MutableArray# RealWorld a)
6574

66-
writeArrayIO :: MutableArray# RealWorld a -> Int# -> a -> IO ()
67-
writeArrayIO arr i x = IO (\s -> (# writeArray# arr i x s, () #))
68-
69-
newArrayIO :: Int# -> a -> IO (Lifted (MutableArray# RealWorld a))
70-
newArrayIO n x = IO $ \s ->
75+
newMutArray :: Int -> a -> IO (MutArray a)
76+
newMutArray (I# n) x = IO $ \s ->
7177
case newArray# n x s of
72-
(# s', arr #) -> (# s', Lifted arr #)
78+
(# s', arr #) -> (# s', MA arr #)
79+
80+
readMutArray :: MutArray a -> Int -> IO a
81+
readMutArray (MA arr) (I# i) = IO (readArray# arr i)
82+
83+
writeMutArray :: MutArray a -> Int -> a -> IO ()
84+
writeMutArray (MA arr) (I# i) x = IO (\s -> (# writeArray# arr i x s, () #))
7385

7486
-- | Convert a list into an array. O(n)
7587
fromList :: [a] -> Array a
7688
fromList xs = unsafeDupablePerformIO $ do
77-
let !(I# n) = length xs
78-
Lifted arr <- newArrayIO n undefined
89+
arr0 <- newMutArray (length xs) undefined
7990
let go _ _ [] = pure ()
80-
go arr i (x:xs') = writeArrayIO arr i x >> go arr (i +# 1#) xs'
81-
go arr 0# xs
82-
Lifted var <- newMutVarIO (Current arr)
83-
pure (DA var)
91+
go arr i (x:xs') = writeMutArray arr i x *> go arr (i + 1) xs'
92+
go arr0 0 xs
93+
v <- newArrayVar (Current arr0)
94+
pure (A v)
8495

85-
cloneMutableArrayIO :: MutableArray# RealWorld a -> Int# -> Int# -> IO (Lifted (MutableArray# RealWorld a))
86-
cloneMutableArrayIO arr off len = IO $ \s ->
96+
cloneMutArray :: MutArray a -> Int -> Int -> IO (MutArray a)
97+
cloneMutArray (MA arr) (I# off) (I# len) = IO $ \s ->
8798
case cloneMutableArray# arr off len s of
88-
(# s', arr' #) -> (# s', Lifted arr' #)
99+
(# s', arr' #) -> (# s', MA arr' #)
100+
101+
sizeofMutArray :: MutArray a -> Int
102+
sizeofMutArray (MA x) = I# (sizeofMutableArray# x)
89103

90-
copyInternalIO :: MutVar# RealWorld (ArrayData a) -> IO (Lifted (MutableArray# RealWorld a))
91-
copyInternalIO v =
92-
readMutVarIO v $ \case
93-
Current arr -> cloneMutableArrayIO arr 0# (sizeofMutableArray# arr)
104+
copyInternal :: ArrayVar a -> IO (MutArray a)
105+
copyInternal v = do
106+
av <- readArrayVar v
107+
case av of
108+
Current arr -> cloneMutArray arr 0 (sizeofMutArray arr)
94109
Diff op v' -> do
95-
Lifted clone <- copyInternalIO v'
96-
appOpIO clone op
97-
pure (Lifted clone)
110+
clone <- copyInternal v'
111+
appOp clone op
112+
pure clone
98113

99114
-- | Converting an array into a list. O(n)
100115
toList :: Array a -> [a]
101-
toList (DA v) = unsafeDupablePerformIO $ do
102-
Lifted arr <- copyInternalIO v
103-
let n = sizeofMutableArray# arr
116+
toList (A v) = unsafeDupablePerformIO $ do
117+
arr <- copyInternal v
118+
let n = sizeofMutArray arr
104119
go i
105-
| isTrue# (i >=# n) = pure []
120+
| i >= n = pure []
106121
| otherwise = do
107-
x <- readArrayIO arr i
108-
xs <- go (i +# 1#)
122+
x <- readMutArray arr i
123+
xs <- go (i + 1)
109124
pure (x : xs)
110-
go 0#
125+
go 0
111126

112127
-- | Indexing an array. O(1)
113128
{-# INLINE (!) #-}
114129
(!) :: Array a -> Int -> a
115-
DA v ! I# i = unsafeDupablePerformIO (helper v i) where
116-
helper v i = readMutVarIO v $ \case
117-
Current arr -> readArrayIO arr i
118-
Diff (Set j x) xs
119-
| isTrue# (i ==# j) -> pure x
120-
| otherwise -> helper xs i
121-
Diff (Swap j1 j2) xs
122-
| isTrue# (i ==# j1) -> helper xs j2
123-
| isTrue# (i ==# j2) -> helper xs j1
124-
| otherwise -> helper xs i
130+
A v0 ! i0 = unsafeDupablePerformIO (go v0 i0) where
131+
go v i = do
132+
dat <- readArrayVar v
133+
case dat of
134+
Current arr -> readMutArray arr i
135+
Diff (Set j x) v'
136+
| i == j -> pure x
137+
| otherwise -> go v' i
138+
Diff (Swap j1 j2) v'
139+
| i == j1 -> go v' j2
140+
| i == j2 -> go v' j1
141+
| otherwise -> go v' i
125142

126143
-- | Indexing an array. O(1)
127144
-- Using the 'Solo' constructor, you can sequence indexing to happen before
128145
-- future updates without having to evaluate the element itself.
129146
{-# INLINE index #-}
130147
index :: Int -> Array a -> Solo a
131-
index (I# i) (DA v) = unsafeDupablePerformIO (helper v i) where
132-
helper v i = readMutVarIO v $ \case
133-
Current arr -> MkSolo <$> readArrayIO arr i
148+
index i0 (A v0) = unsafeDupablePerformIO (go v0 i0) where
149+
go v i = do
150+
dat <- readArrayVar v
151+
case dat of
152+
Current arr -> MkSolo <$> readMutArray arr i
134153
Diff (Set j x) xs
135-
| isTrue# (i ==# j) -> pure (MkSolo x)
136-
| otherwise -> helper xs i
154+
| i == j -> pure (MkSolo x)
155+
| otherwise -> go xs i
137156
Diff (Swap j1 j2) xs
138-
| isTrue# (i ==# j1) -> helper xs j2
139-
| isTrue# (i ==# j2) -> helper xs j1
140-
| otherwise -> helper xs i
141-
142-
{-# INLINE invertIO #-}
143-
invertIO :: MutableArray# RealWorld a -> Op a -> IO (Op a)
144-
invertIO _ (Swap i j) = pure (Swap i j)
145-
invertIO arr (Set i _) = do
146-
y <- readArrayIO arr i
157+
| i == j1 -> go xs j2
158+
| i == j2 -> go xs j1
159+
| otherwise -> go xs i
160+
161+
{-# INLINE invert #-}
162+
invert :: MutArray a -> Op a -> IO (Op a)
163+
invert _ (Swap i j) = pure (Swap i j)
164+
invert arr (Set i _) = do
165+
y <- readMutArray arr i
147166
pure (Set i y)
148167

149-
{-# INLINE appOpIO #-}
150-
appOpIO :: MutableArray# RealWorld a -> Op a -> IO ()
151-
appOpIO arr (Set i x) = writeArrayIO arr i x
152-
appOpIO arr (Swap i j) = do
153-
x <- readArrayIO arr i
154-
y <- readArrayIO arr j
155-
writeArrayIO arr i y
156-
writeArrayIO arr j x
168+
{-# INLINE appOp #-}
169+
appOp :: MutArray a -> Op a -> IO ()
170+
appOp arr (Set i x) = writeMutArray arr i x
171+
appOp arr (Swap i j) = do
172+
x <- readMutArray arr i
173+
y <- readMutArray arr j
174+
writeMutArray arr i y
175+
writeMutArray arr j x
157176

158177
{-# INLINE appDiffOp #-}
159178
appDiffOp :: Op a -> Array a -> Array a
160-
appDiffOp op (DA v) = unsafeDupablePerformIO $
161-
readMutVarIO v $ \case
179+
appDiffOp op (A v) = unsafeDupablePerformIO $ do
180+
dat <- readArrayVar v
181+
case dat of
162182
xs@(Current arr) -> do
163-
op' <- invertIO arr op
164-
appOpIO arr op
165-
Lifted v' <- newMutVarIO xs
166-
writeMutVarIO v (Diff op' v')
167-
pure (DA v')
183+
op' <- invert arr op
184+
appOp arr op
185+
v' <- newArrayVar xs
186+
writeArrayVar v (Diff op' v')
187+
pure (A v')
168188
Diff op' v' -> do
169-
Lifted arr <- copyInternalIO v'
170-
appOpIO arr op'
171-
appOpIO arr op
172-
Lifted v'' <- newMutVarIO (Current arr)
173-
pure (DA v'')
189+
-- TODO: pointer inversion instead of copy
190+
-- first invert all pointers until Current
191+
-- then apply all updates until back at v
192+
-- then do the same as above
193+
arr <- copyInternal v'
194+
appOp arr op'
195+
appOp arr op
196+
v'' <- newArrayVar (Current arr)
197+
pure (A v'')
174198

175199
-- | Update the array element at a given position to a new value. O(1)
176200
{-# INLINE set #-}
177201
set :: Int -> a -> Array a -> Array a
178-
set (I# i) x = appDiffOp (Set i x)
202+
set i x = appDiffOp (Set i x)
179203

180204
-- | Swap two elements in an array. O(1)
181205
{-# INLINE swap #-}
182206
swap :: Int -> Int -> Array a -> Array a
183-
swap (I# i) (I# j) = appDiffOp (Swap i j)
207+
swap i j = appDiffOp (Swap i j)
184208

185209
-- | Copy an array. O(n)
186210
-- This detaches any future updates from old versions of the array.
187211
-- Use this when you know you will be updating a large part of an array.
188212
copy :: Array a -> Array a
189-
copy (DA v) = unsafeDupablePerformIO $ do
190-
Lifted arr <- copyInternalIO v
191-
Lifted var <- newMutVarIO (Current arr)
192-
pure (DA var)
213+
copy (A v) = unsafeDupablePerformIO $ do
214+
arr <- copyInternal v
215+
var <- newArrayVar (Current arr)
216+
pure (A var)

0 commit comments

Comments
 (0)