1
- {-# LANGUAGE BangPatterns, ForeignFunctionInterface #-}
1
+ {-# LANGUAGE BangPatterns #-}
2
+ {-# LANGUAGE CPP #-}
3
+ {-# LANGUAGE ForeignFunctionInterface #-}
4
+ {-# LANGUAGE MagicHash #-}
5
+ {-# LANGUAGE TypeApplications #-}
6
+ {-# LANGUAGE UnboxedTuples #-}
2
7
-- | An atomic integer value. All operations are thread safe.
3
8
module Data.Atomic
4
9
(
@@ -12,33 +17,52 @@ module Data.Atomic
12
17
, subtract
13
18
) where
14
19
15
- import Data.Int (Int64 )
16
- import Foreign.ForeignPtr (ForeignPtr , mallocForeignPtr , withForeignPtr )
17
- import Foreign.Ptr (Ptr )
18
- import Foreign.Storable (poke )
19
20
import Prelude hiding (read , subtract )
20
21
22
+ #include "MachDeps.h"
23
+
24
+ -- We want an atomic with at least 64 bits in order to avoid overflow.
25
+
26
+ #if WORD_SIZE_IN_BITS >= 64
27
+
28
+ -- If the machine word size is 64 bits, we can use GHC's atomic primops
29
+ -- (`fetchAddIntArray`) to implement our atomic.
30
+ --
31
+ -- We pad to 64-bytes (an x86 cache line) to try to avoid false sharing.
32
+ --
33
+ -- Implementation note: We always make sure to interact with the
34
+ -- `MutableByteArray` at element type `Int`.
35
+
36
+ import Control.Monad (void )
37
+ import Control.Monad.Primitive (RealWorld )
38
+ import Data.Atomics (fetchAddIntArray , fetchSubIntArray )
39
+ import Data.Int (Int64 )
40
+ import Data.Primitive.ByteArray
41
+ import Data.Primitive.MachDeps (sIZEOF_INT )
42
+ import Control.Exception (assert )
43
+
44
+ sIZEOF_CACHELINE :: Int
45
+ sIZEOF_CACHELINE = 64
46
+ {-# INLINE sIZEOF_CACHELINE #-}
47
+
21
48
-- | A mutable, atomic integer.
22
- newtype Atomic = C (ForeignPtr Int64 )
49
+ newtype Atomic = C (MutableByteArray RealWorld )
23
50
24
- -- | Create a new, zero initialized, atomic.
51
+ -- | Create a new atomic.
25
52
new :: Int64 -> IO Atomic
26
53
new n = do
27
- fp <- mallocForeignPtr
28
- withForeignPtr fp $ \ p -> poke p n
29
- return $ C fp
54
+ arr <- newAlignedPinnedByteArray sIZEOF_CACHELINE sIZEOF_CACHELINE
55
+ writeByteArray @ Int arr 0 (fromIntegral n)
56
+ -- out of principle:
57
+ assert (sIZEOF_INT < sIZEOF_CACHELINE) $
58
+ pure (C arr)
30
59
31
60
read :: Atomic -> IO Int64
32
- read (C fp) = withForeignPtr fp cRead
33
-
34
- foreign import ccall unsafe " hs_atomic_read" cRead :: Ptr Int64 -> IO Int64
61
+ read (C arr) = fromIntegral <$> readByteArray @ Int arr 0
35
62
36
63
-- | Set the atomic to the given value.
37
64
write :: Atomic -> Int64 -> IO ()
38
- write (C fp) n = withForeignPtr fp $ \ p -> cWrite p n
39
-
40
- foreign import ccall unsafe " hs_atomic_write" cWrite
41
- :: Ptr Int64 -> Int64 -> IO ()
65
+ write (C arr) n = writeByteArray @ Int arr 0 (fromIntegral n)
42
66
43
67
-- | Increase the atomic by one.
44
68
inc :: Atomic -> IO ()
@@ -50,15 +74,49 @@ dec atomic = subtract atomic 1
50
74
51
75
-- | Increase the atomic by the given amount.
52
76
add :: Atomic -> Int64 -> IO ()
53
- add (C fp ) n = withForeignPtr fp $ \ p -> cAdd p n
77
+ add (C arr ) n = void $ fetchAddIntArray arr 0 ( fromIntegral n)
54
78
55
79
-- | Decrease the atomic by the given amount.
56
80
subtract :: Atomic -> Int64 -> IO ()
57
- subtract (C fp ) n = withForeignPtr fp $ \ p -> cSubtract p n
81
+ subtract (C arr ) n = void $ fetchSubIntArray arr 0 ( fromIntegral n)
58
82
59
- -- | Increase the atomic by the given amount.
60
- foreign import ccall unsafe " hs_atomic_add" cAdd :: Ptr Int64 -> Int64 -> IO ()
83
+ #else
84
+
85
+ -- If the machine word size less than 64 bits, we fall back to `IORef`s
86
+ -- and `atomicModifyIORefCAS`. This is much slower.
87
+
88
+ import Data.Atomics (atomicModifyIORefCAS_ )
89
+ import Data.Int (Int64 )
90
+ import Data.IORef
91
+
92
+ -- | A mutable, atomic integer.
93
+ newtype Atomic = C (IORef Int64 )
94
+
95
+ -- | Create a new atomic.
96
+ new :: Int64 -> IO Atomic
97
+ new n = C <$> newIORef n
98
+
99
+ read :: Atomic -> IO Int64
100
+ read (C ref) = readIORef ref
101
+
102
+ -- | Set the atomic to the given value.
103
+ write :: Atomic -> Int64 -> IO ()
104
+ write (C ref) = writeIORef ref
105
+
106
+ -- | Increase the atomic by one.
107
+ inc :: Atomic -> IO ()
108
+ inc atomic = add atomic 1
109
+
110
+ -- | Decrease the atomic by one.
111
+ dec :: Atomic -> IO ()
112
+ dec atomic = subtract atomic 1
61
113
62
114
-- | Increase the atomic by the given amount.
63
- foreign import ccall unsafe " hs_atomic_subtract" cSubtract
64
- :: Ptr Int64 -> Int64 -> IO ()
115
+ add :: Atomic -> Int64 -> IO ()
116
+ add (C ref) n = atomicModifyIORefCAS_ ref $ \ x -> x+ n
117
+
118
+ -- | Decrease the atomic by the given amount.
119
+ subtract :: Atomic -> Int64 -> IO ()
120
+ subtract (C ref) n = atomicModifyIORefCAS_ ref $ \ x -> x- n
121
+
122
+ #endif
0 commit comments