diff options
author | Cheng Shao <astrohavoc@gmail.com> | 2022-09-09 17:45:45 +0000 |
---|---|---|
committer | Cheng Shao <astrohavoc@gmail.com> | 2022-11-16 09:16:29 +0000 |
commit | 08bf28819b78e740550a73a90eda62cce8d21c90 (patch) | |
tree | 38dd14258332f5fc8ca4798d37968723a0ad873b | |
parent | 02d3511b8d248ea9429512830f8f17b31688a6a6 (diff) | |
download | haskell-08bf28819b78e740550a73a90eda62cce8d21c90.tar.gz |
base: make Foreign.Marshal.Pool use RTS internal arena for allocation
`Foreign.Marshal.Pool` used to call `malloc` once for each allocation
request. Each `Pool` maintained a list of allocated pointers, and
traverses the list to `free` each one of those pointers. The extra O(n)
overhead is apparently bad for a `Pool` that serves a lot of small
allocation requests.
This patch uses the RTS internal arena to implement `Pool`, with these
benefits:
- Gets rid of the extra O(n) overhead.
- The RTS arena is simply a bump allocator backed by the block
allocator, each allocation request is likely faster than a libc
`malloc` call.
Closes #14762 #18338.
-rw-r--r-- | libraries/base/Foreign/Marshal/Pool.hs | 48 | ||||
-rw-r--r-- | libraries/base/changelog.md | 8 | ||||
-rw-r--r-- | rts/Arena.h | 6 |
3 files changed, 32 insertions, 30 deletions
diff --git a/libraries/base/Foreign/Marshal/Pool.hs b/libraries/base/Foreign/Marshal/Pool.hs index 8d704c1a2d..7e6443b6ea 100644 --- a/libraries/base/Foreign/Marshal/Pool.hs +++ b/libraries/base/Foreign/Marshal/Pool.hs @@ -46,19 +46,18 @@ module Foreign.Marshal.Pool ( pooledNewArray0 ) where -import GHC.Base ( Int, Monad(..), (.), liftM, not ) +import GHC.Base ( Int, Monad(..) ) import GHC.Err ( undefined ) import GHC.Exception ( throw ) import GHC.IO ( IO, mask, catchAny ) -import GHC.IORef ( IORef, newIORef, readIORef, writeIORef ) -import GHC.List ( elem, length ) +import GHC.List ( length ) import GHC.Num ( Num(..) ) +import GHC.Real ( fromIntegral ) -import Data.OldList ( delete ) -import Foreign.Marshal.Alloc ( mallocBytes, reallocBytes, free ) +import Foreign.C.Types ( CSize(..) ) import Foreign.Marshal.Array ( pokeArray, pokeArray0 ) -import Foreign.Marshal.Error ( throwIf ) -import Foreign.Ptr ( Ptr, castPtr ) +import Foreign.Marshal.Utils ( moveBytes ) +import Foreign.Ptr ( Ptr ) import Foreign.Storable ( Storable(sizeOf, poke) ) -------------------------------------------------------------------------------- @@ -68,20 +67,18 @@ import Foreign.Storable ( Storable(sizeOf, poke) ) -- | A memory pool. -newtype Pool = Pool (IORef [Ptr ()]) +newtype Pool = Pool (Ptr ()) -- | Allocate a fresh memory pool. newPool :: IO Pool -newPool = liftM Pool (newIORef []) +newPool = c_newArena -- | Deallocate a memory pool and everything which has been allocated in the -- pool itself. freePool :: Pool -> IO () -freePool (Pool pool) = readIORef pool >>= freeAll - where freeAll [] = return () - freeAll (p:ps) = free p >> freeAll ps +freePool = c_arenaFree -- | Execute an action with a fresh memory pool, which gets automatically -- deallocated (including its contents) after the action has finished. @@ -108,11 +105,7 @@ pooledMalloc pool = pooledMallocBytes pool (sizeOf (undefined :: a)) -- | Allocate the given number of bytes of storage in the pool. pooledMallocBytes :: Pool -> Int -> IO (Ptr a) -pooledMallocBytes (Pool pool) size = do - ptr <- mallocBytes size - ptrs <- readIORef pool - writeIORef pool (ptr:ptrs) - return (castPtr ptr) +pooledMallocBytes pool size = c_arenaAlloc pool (fromIntegral size) -- | Adjust the storage area for an element in the pool to the given size of -- the required type. @@ -120,16 +113,15 @@ pooledMallocBytes (Pool pool) size = do pooledRealloc :: forall a . Storable a => Pool -> Ptr a -> IO (Ptr a) pooledRealloc pool ptr = pooledReallocBytes pool ptr (sizeOf (undefined :: a)) --- | Adjust the storage area for an element in the pool to the given size. +-- | Adjust the storage area for an element in the pool to the given size. Note +-- that the previously allocated space is still retained in the same 'Pool' and +-- will only be freed when the entire 'Pool' is freed. pooledReallocBytes :: Pool -> Ptr a -> Int -> IO (Ptr a) -pooledReallocBytes (Pool pool) ptr size = do - let cPtr = castPtr ptr - _ <- throwIf (not . (cPtr `elem`)) (\_ -> "pointer not in pool") (readIORef pool) - newPtr <- reallocBytes cPtr size - ptrs <- readIORef pool - writeIORef pool (newPtr : delete cPtr ptrs) - return (castPtr newPtr) +pooledReallocBytes pool ptr size = do + newPtr <- pooledMallocBytes pool size + moveBytes newPtr ptr size + return newPtr -- | Allocate storage for the given number of elements of a storable type in the -- pool. @@ -185,3 +177,9 @@ pooledNewArray0 pool marker vals = do ptr <- pooledMallocArray0 pool (length vals) pokeArray0 marker ptr vals return ptr + +foreign import ccall unsafe "newArena" c_newArena :: IO Pool + +foreign import ccall unsafe "arenaAlloc" c_arenaAlloc :: Pool -> CSize -> IO (Ptr a) + +foreign import ccall unsafe "arenaFree" c_arenaFree :: Pool -> IO () diff --git a/libraries/base/changelog.md b/libraries/base/changelog.md index d955cd59a4..cc5f44ef96 100644 --- a/libraries/base/changelog.md +++ b/libraries/base/changelog.md @@ -31,8 +31,8 @@ as well as [the migration guide](https://github.com/haskell/core-libraries-committee/blob/main/guides/export-lifta2-prelude.md) * Update to [Unicode 15.0.0](https://www.unicode.org/versions/Unicode15.0.0/). * Add standard Unicode case predicates `isUpperCase` and `isLowerCase` to - `GHC.Unicode` and `Data.Char`. These predicates use the standard Unicode - case properties and are more intuitive than `isUpper` and `isLower`. See + `GHC.Unicode` and `Data.Char`. These predicates use the standard Unicode + case properties and are more intuitive than `isUpper` and `isLower`. See [CLC proposal #90](https://github.com/haskell/core-libraries-committee/issues/90). * Add `Eq` and `Ord` instances for `Generically1`. * Relax instances for Functor combinators; put superclass on Class1 and Class2 @@ -50,6 +50,10 @@ * The `Enum` instance of `Down a` now enumerates values in the opposite order as the `Enum a` instance, per [CLC proposal #51](https://github.com/haskell/core-libraries-committee/issues/51). + * `Foreign.Marshal.Pool` now uses the RTS internal arena instead of libc + `malloc` for allocation. It avoids the O(n) overhead of maintaining a list + of individually allocated pointers as well as freeing each one of them when + freeing a `Pool`. (#14762) (#18338) ## 4.17.0.0 *August 2022* diff --git a/rts/Arena.h b/rts/Arena.h index 49298713ab..8a7b304a7a 100644 --- a/rts/Arena.h +++ b/rts/Arena.h @@ -10,13 +10,13 @@ typedef struct _Arena Arena; // Start a new arena -RTS_PRIVATE Arena * newArena ( void ); +Arena * newArena ( void ); // Allocate memory in an arena -RTS_PRIVATE void * arenaAlloc ( Arena *, size_t ); +void * arenaAlloc ( Arena *, size_t ); // Free an entire arena -RTS_PRIVATE void arenaFree ( Arena * ); +void arenaFree ( Arena * ); // For internal use only: RTS_PRIVATE unsigned long arenaBlocks( void ); |