compact 0.1.0.0 → 0.1.0.1
raw patch · 5 files changed
+227/−3 lines, 5 filesdep ~base
Dependency ranges changed: base
Files
- ChangeLog.md +2/−2
- README.md +195/−0
- compact.cabal +3/−1
- tests/sample1.hs +17/−0
- tests/sample2.hs +10/−0
ChangeLog.md view
@@ -1,5 +1,5 @@ # Revision history for compact -## 0.1.0.0 -- YYYY-mm-dd+## 0.1.0.0 -- 2017-02-27 -* First version. Released on an unsuspecting world.+* First version.
+ README.md view
@@ -0,0 +1,195 @@+# compact++[](https://hackage.haskell.org/package/compact)++*Non-GC'd, contiguous storage for immutable data structures.*++This package provides user-facing APIs for working with "compact regions", which+hold a fully evaluated Haskell object graph. These regions maintain the+invariant that no pointers live inside the struct that point outside it, which+ensures efficient garbage collection without ever reading the structure contents+(effectively, it works as a manually managed "oldest generation" which is never+freed until the whole is released).++When would you want to use a compact region? The simplest use case is this: you+have some extremely large, long-lived, pointer data structure which GHC has+uselessly been tracing when you have a major collection. If you place this+structure in a compact region, after the initial cost of copying the data into+the region, you should see a speedup in your major GC runs.++This package is currently highly experimental, but we hope it may be useful to+some people. It is GHC 8.2 only. The bare-bones library that ships with GHC is+ghc-compact.++## Quick start++* Import `Data.Compact`++* Put some data in a compact region with `compact :: a -> IO (Compact a)`,+ e.g., `cr <- compact someBigDataStructure`, fully evaluating it in+ the process.++* Use `getCompact :: Compact a -> a` to get a pointer inside the region,+ e.g., `operateOnDataStructure (getCompact cr)`. The data pointed to+ by these pointers will not participate in GC.++* Import `Data.Compact.Serialize` to write and read compact regions from files.++## Tutorial++**Garbage collection savings.** It's a little difficult to construct a+compelling, small example showing the benefit, but here is a very simple case+from the `nofib` test suite, the `spellcheck` program. `spellcheck` is a very+simple program which reads a dictionary into a set, and then tests an input+word-by-word to see if it is in the set or not (yes, it is a *very* simple+spell checker):++```+import System.Environment (getArgs)+import qualified Data.Set as Set+import System.IO++main = do+ [file1,file2] <- getArgs+ dict <- readFileLatin1 file1+ input <- readFileLatin1 file2+ let set = Set.fromList (words dict)+ let tocheck = words input+ print (filter (`Set.notMember` set) tocheck)++readFileLatin1 f = do+ h <- openFile f ReadMode+ hSetEncoding h latin1+ hGetContents h+```++Converting this program to use a compact region on the dictionary is very+simple: add `import Data.Compact`, and convert `let set = Set.fromList (words+dict)` to read `set <- fmap getCompact (compact (Set.fromList (words dict)))`:++```+import System.Environment (getArgs)+import qualified Data.Set as Set+import System.IO+import Data.Compact -- **++main = do+ [file1,file2] <- getArgs+ dict <- readFileLatin1 file1+ input <- readFileLatin1 file2+ set <- fmap getCompact (compact (Set.fromList (words dict))) -- ***+ let tocheck = words input+ print (filter (`Set.notMember` set) tocheck)++readFileLatin1 f = do+ h <- openFile f ReadMode+ hSetEncoding h latin1+ hGetContents h+```++Breaking down the new line: `compact` takes an argument `a` which must be pure+and immutable and then copies it into a compact region. This function returns a+`Compact a` pointer, which is simultaneously a handle to the compact region as+well as the data you copied into it. You get back the actual `a` data that+lives in the region using `getCompact`.++Using the sample `nofib` input+([words](https://github.com/ghc/nofib/blob/master/gc/spellcheck/words) and+[input](https://github.com/ghc/nofib/blob/master/gc/spellcheck/input>)), we can take+a look at our GC stats before and after the change. To make the effect more+pronounced, I've reduced the allocation area size to 256K, so that we do more+major collections. Here are the stats with the original:++```+ 1,606,462,200 bytes allocated in the heap+ 727,499,032 bytes copied during GC+ 24,050,160 bytes maximum residency (21 sample(s))+ 107,144 bytes maximum slop+ 71 MB total memory in use (0 MB lost due to fragmentation)++ Tot time (elapsed) Avg pause Max pause+ Gen 0 6119 colls, 0 par 0.743s 0.754s 0.0001s 0.0023s+ Gen 1 21 colls, 0 par 0.608s 0.611s 0.0291s 0.0582s++ INIT time 0.000s ( 0.000s elapsed)+ MUT time 2.012s ( 2.024s elapsed)+ GC time 1.350s ( 1.365s elapsed)+ EXIT time 0.000s ( 0.000s elapsed)+ Total time 3.363s ( 3.389s elapsed)++ %GC time 40.2% (40.3% elapsed)++ Alloc rate 798,416,807 bytes per MUT second++ Productivity 59.8% of total user, 59.7% of total elapsed+```++Here are the stats with compact regions:++```+ 1,630,448,408 bytes allocated in the heap+ 488,392,976 bytes copied during GC+ 24,104,152 bytes maximum residency (21 sample(s))+ 76,144 bytes maximum slop+ 55 MB total memory in use (0 MB lost due to fragmentation)++ Tot time (elapsed) Avg pause Max pause+ Gen 0 6119 colls, 0 par 0.755s 0.770s 0.0001s 0.0017s+ Gen 1 21 colls, 0 par 0.147s 0.147s 0.0070s 0.0462s++ INIT time 0.000s ( 0.000s elapsed)+ MUT time 1.999s ( 2.054s elapsed)+ GC time 0.902s ( 0.918s elapsed)+ EXIT time 0.000s ( 0.000s elapsed)+ Total time 2.901s ( 2.972s elapsed)++ %GC time 31.1% (30.9% elapsed)++ Alloc rate 815,689,434 bytes per MUT second++ Productivity 68.9% of total user, 69.1% of total elapsed+```++You can see that while the version of the program with compact regions allocates+slightly more (since it performs a copy on the set), it copies nearly half as+much data during GC, reducing the time spent in major GCs by a factor of three.+On this particular example, you don't actually save that much time overall+(since the bulk of execution is spent in the mutator)--a reminder that one+should always measure before one optimizes.++**Serializing to disk.**+You can take the data in a compact region and save it to disk, so that you can+load it up at a later point in time. This functionality is provided by+`Data.Compact.Serialized`: `writeCompact` and `unsafeReadCompact` let you+write a compact to a file, and read it back again:++```+{-# LANGUAGE TypeApplications #-}+import Data.Compact+import Data.Compact.Serialize+main = do+ orig_c <- compact ("I want to serialize this", True)+ writeCompact @(String, Bool) "somefile" orig_c+ res <- unsafeReadCompact @(String, Bool) "somefile"+ case res of+ Left err -> fail err+ Right c -> print (getCompact c)+```++Compact regions written to handles this way are subject to some+restrictions:++* Our binary representation contains direct pointers to the info+ tables of objects in the region. This means that the info tables+ of the receiving process must be laid out in exactly the same+ way as from the original process; in practice, this means using+ static linking, using the exact same binary and turning off ASLR. This+ API does NOT do any safety checking and will probably segfault if you+ get it wrong. DO NOT run `unsafeReadCompact` on untrusted input.++* You must read out the value at the correct type. We will+ check this for you and raise an error if the types do not match.+ To tell `unsafeReadCompact` what type it should read out with,+ the `TypeApplications` extension may come in handy (this extension+ is guaranteed to be available, since compact only supports GHC 8.2+ or later!)
compact.cabal view
@@ -1,5 +1,5 @@ name: compact-version: 0.1.0.0+version: 0.1.0.1 synopsis: Non-GC'd, contiguous storage for immutable data structures description: This package provides user-facing APIs for working with@@ -17,11 +17,13 @@ license-file: LICENSE author: Edward Z. Yang, Ben Gamari maintainer: ezyang@mit.edu, ben@smart-cactus.org+homepage: https://github.com/ezyang/compact copyright: (c) 2017 Edward Z. Yang, Ben Gamari category: Data build-type: Simple extra-source-files: ChangeLog.md cabal-version: >=1.10+extra-source-files: README.md tests/sample1.hs tests/sample2.hs library exposed-modules: Data.Compact
+ tests/sample1.hs view
@@ -0,0 +1,17 @@+import System.Environment (getArgs)+import qualified Data.Set as Set+import System.IO+import Data.Compact -- **++main = do+ [file1,file2] <- getArgs+ dict <- readFileLatin1 file1+ input <- readFileLatin1 file2+ set <- fmap getCompact (compact (Set.fromList (words dict))) -- ***+ let tocheck = words input+ print (filter (`Set.notMember` set) tocheck)++readFileLatin1 f = do+ h <- openFile f ReadMode+ hSetEncoding h latin1+ hGetContents h
+ tests/sample2.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE TypeApplications #-}+import Data.Compact+import Data.Compact.Serialize+main = do+ orig_c <- compact ("I want to serialize this", True)+ writeCompact @(String, Bool) "somefile" orig_c+ res <- unsafeReadCompact @(String, Bool) "somefile"+ case res of+ Left err -> fail err+ Right c -> print (getCompact c)