packages feed

symbolize 1.0.2.3 → 1.0.2.4

raw patch · 5 files changed

+82/−13 lines, 5 filesdep +tasty-benchdep +vectorPVP ok

version bump matches the API change (PVP)

Dependencies added: tasty-bench, vector

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -8,6 +8,11 @@  ## Unreleased +## 1.0.2.4++- Fix too eager creation of weak pointers, resulting in many needless allocations. This greatly reduces memory pressure and improves speed when inserting many symbols in a short amount of time! +- Only calculate the SipHash hash of an incoming text _once_ when inserting. (c.f. [#8](https://github.com/Qqwy/haskell-symbolize/issues/8))+ ## 1.0.2.3  - Swaps the internal usage of lists in the global symbol table for `NonEmpty` lists, since they will never be empty. (PR [#5](https://github.com/Qqwy/haskell-symbolize/pull/5)). Thank you, @Bodigrim!
+ benchmarks/SymbolizeBench.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE GHC2021 #-}+{-# LANGUAGE OverloadedLists #-}+import Test.Tasty.Bench++import Symbolize qualified++import Data.Text (Text)+import Data.Text qualified as Text+import Data.Vector (Vector)+import Data.Vector qualified as V++mkTexts :: IO (Vector Text)+mkTexts = do+    let vec = Text.pack . show @Int <$> [0..3200]+    pure vec++roundtripMany :: Vector Text -> Vector Text+roundtripMany = fmap (\val -> Symbolize.unintern $! Symbolize.intern $! val)+++main :: IO ()+main = defaultMain+  [ env mkTexts $ \texts ->+    bgroup "intern/unintern roundtrip, no duplicates"+    [ bench "10"  $ nf roundtripMany $! V.take 10 texts+    , bench "100" $ nf roundtripMany $! V.take 100 texts+    , bench "200" $ nf roundtripMany $! V.take 200 texts+    , bench "400" $ nf roundtripMany $! V.take 400 texts+    , bench "800" $ nf roundtripMany $! V.take 800 texts+    , bench "1600" $ nf roundtripMany $! V.take 1600 texts+    , bench "3200" $ nf roundtripMany $! V.take 3200 texts+    ]+  ]
src/Symbolize/SymbolTable.hs view
@@ -79,26 +79,27 @@ {-# INLINE insertGlobal #-} insertGlobal ba# = do   GlobalSymbolTable gsymtab sipkey <- globalSymbolTable-  let !key = calculateHash sipkey ba#+  let !hash = calculateHash sipkey ba#   -- SAFETY: If the table IORef contested,    -- this might trigger `weak` creation for the same bytestring from multiple threads   -- at the same time.   -- But finalization is idempotent, and only when a thread finally wins the Compare-and-Swap   -- will its `weak` pointer be inserted (or alternatively another previously-inserted `ba` returned).   -- So once this function returns, we can be sure we've returned a deduplicated ByteArray-  !weak <- mkWeakSymbol ba# (removeGlobal key)   IORef.atomicModifyIORef' gsymtab $ \table ->-    case lookup ba# sipkey table of+    case lookup ba# hash table of       Just ba -> (table, ba)       Nothing ->-        (insert key weak table, ByteArray ba#)+        let !weak = Accursed.accursedUnutterablePerformIO (mkWeakSymbol ba# (removeGlobal hash))+        in (insert hash weak table, ByteArray ba#)  lookupGlobal :: ByteArray# -> IO (Maybe ByteArray) {-# INLINE lookupGlobal #-} lookupGlobal ba# = do   GlobalSymbolTable gsymtab sipkey <- globalSymbolTable   table <- IORef.readIORef gsymtab-  pure (lookup ba# sipkey table)+  let hash = calculateHash sipkey ba#+  pure (lookup ba# hash table)  removeGlobal :: Hash -> IO () {-# INLINE removeGlobal #-}@@ -113,11 +114,10 @@   let table' = IntMap.alter (Just . maybe (pure weak) (NonEmpty.cons weak)) (hashToInt key) table    in SymbolTable table' -lookup :: ByteArray# -> SipHash.SipKey -> SymbolTable -> Maybe ByteArray+lookup :: ByteArray# -> Hash -> SymbolTable -> Maybe ByteArray {-# INLINE lookup #-}-lookup ba# sipkey (SymbolTable table) = do-  let !key = calculateHash sipkey ba#-  weaks <- IntMap.lookup (hashToInt key) table+lookup ba# hash (SymbolTable table) = do+  weaks <- IntMap.lookup (hashToInt hash) table   Foldable.find (\other -> other == ByteArray ba#) (aliveWeaks weaks)  remove :: Hash -> SymbolTable -> SymbolTable
symbolize.cabal view
@@ -1,11 +1,11 @@ cabal-version: 2.2 --- This file has been generated from package.yaml by hpack version 0.35.2.+-- This file has been generated from package.yaml by hpack version 0.37.0. -- -- see: https://github.com/sol/hpack  name:           symbolize-version:        1.0.2.3+version:        1.0.2.4 synopsis:       Efficient global Symbol table, with Garbage Collection. description:    Symbols, also known as Atoms or Interned Strings, are a common technique                 to reduce memory usage and improve performance when using many small strings:@@ -146,4 +146,35 @@     , tasty-hunit     , text >=2.0 && <2.2     , text-short >=0.1.0 && <0.2+  default-language: Haskell2010++benchmark symbolize-bench+  type: exitcode-stdio-1.0+  main-is: SymbolizeBench.hs+  other-modules:+      Paths_symbolize+  autogen-modules:+      Paths_symbolize+  hs-source-dirs:+      benchmarks+  default-extensions:+      BangPatterns+      OverloadedStrings+      DeriveAnyClass+      TypeApplications+      NamedFieldPuns+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -with-rtsopts=-A32m -fproc-alignment=64+  build-depends:+      base >=4.7 && <5+    , binary >=0.8.9 && <0.9+    , bytestring >=0.11.0 && <0.13+    , containers >=0.6.0 && <0.8+    , deepseq >=1.4.0 && <1.6+    , hashable >=1.4.0 && <1.6+    , random >=1.2 && <2+    , symbolize+    , tasty-bench+    , text >=2.0 && <2.2+    , text-short >=0.1.0 && <0.2+    , vector   default-language: Haskell2010
test/suite/SymbolizeTest.hs view
@@ -13,7 +13,7 @@ -- import Test.Tasty.HUnit  hprop_symbolTableIsIdempotent :: Property-hprop_symbolTableIsIdempotent = withTests 1000 $ property $ do+hprop_symbolTableIsIdempotent = withTests 2000 $ property $ do   texts <- forAll $ Gen.list (Range.linear 0 200) (Gen.text (Range.linear 0 20) Gen.unicode)   let !symbols = fmap Symbolize.intern texts   annotateShow (fmap Data.Hashable.hash symbols)@@ -22,7 +22,7 @@   texts2 === texts  hprop_concurrentAccessDoesNotCorruptTable :: Property-hprop_concurrentAccessDoesNotCorruptTable = withTests 500 $ property $ do+hprop_concurrentAccessDoesNotCorruptTable = withTests 2000 $ property $ do   let numCores = 8   texts <- forAll $ Gen.list (Range.linear 0 200) (Gen.text (Range.linear 0 20) Gen.unicode)   results <- liftIO $ Control.Concurrent.Async.forConcurrently [(1 :: Integer) .. numCores] $ \_ -> do