Skip to content

Commit 520d70a

Browse files
committed
improve Natural -> ByteString
1 parent 933eb17 commit 520d70a

File tree

7 files changed

+142
-26
lines changed

7 files changed

+142
-26
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
module Benchmark.Nockma.Encoding.Natural where
2+
3+
import Juvix.Prelude
4+
import System.Random
5+
import Test.Tasty.Bench
6+
7+
randomNatural :: IO Natural
8+
randomNatural = do
9+
sg <- getStdGen
10+
let numDigitsMin = 100000
11+
numDigitsMax = 1000000
12+
return (fst (uniformR (10 ^ numDigitsMin, 10 ^ numDigitsMax) sg))
13+
14+
bm :: Benchmark
15+
bm =
16+
let old = padByteString8 . naturalToByteStringOld
17+
new = padByteString8 . naturalToByteString
18+
in bgroup
19+
"Natural -> ByteString"
20+
[ env
21+
(randomNatural)
22+
(\nat -> bench "Old" (nf old nat)),
23+
env
24+
(randomNatural)
25+
(\nat -> bench "New" (nf new nat))
26+
-- env
27+
-- (randomNatural)
28+
-- ( \nat ->
29+
-- bench
30+
-- "TEST"
31+
-- ( nf
32+
-- ( \i ->
33+
-- if old i == new i
34+
-- then True
35+
-- else error "wrong"
36+
-- )
37+
-- nat
38+
-- )
39+
-- )
40+
]

bench2/Main.hs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ module Main where
22

33
import Benchmark.Effect qualified as Effect
44
import Benchmark.Nockma qualified as Nockma
5+
import Benchmark.Nockma.Encoding.Natural as Natural
56
import Juvix.Prelude
67
import Test.Tasty.Bench
78

89
main :: IO ()
910
main =
1011
defaultMain
11-
[ Effect.bm,
12-
Nockma.bm
12+
[ -- Effect.bm,
13+
-- Nockma.bm
14+
Natural.bm
1315
]

src/Juvix/Compiler/Core/Evaluator.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Juvix.Compiler.Core.Info qualified as Info
1616
import Juvix.Compiler.Core.Info.NoDisplayInfo
1717
import Juvix.Compiler.Core.Pretty
1818
import Juvix.Compiler.Nockma.Encoding qualified as Encoding
19-
import Juvix.Compiler.Nockma.Encoding.ByteString (byteStringToIntegerLE, naturalToByteStringLELen)
19+
import Juvix.Compiler.Nockma.Encoding.ByteString (byteStringToIntegerLE)
2020
import Juvix.Compiler.Nockma.Encoding.Ed25519 qualified as E
2121
import Juvix.Compiler.Store.Core.Extra qualified as Store
2222
import Juvix.Data.Field

src/Juvix/Compiler/Nockma/Encoding/ByteString.hs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1+
{-# LANGUAGE MagicHash #-}
2+
13
module Juvix.Compiler.Nockma.Encoding.ByteString where
24

35
import Crypto.Hash.SHA256 qualified as SHA256
46
import Data.Bit (Bit)
57
import Data.Bit qualified as Bit
6-
import Data.Bits
78
import Data.ByteString qualified as BS
89
import Data.ByteString.Base64 qualified as Base64
9-
import Data.ByteString.Builder qualified as BS
1010
import Juvix.Compiler.Nockma.Encoding.Base
1111
import Juvix.Compiler.Nockma.Encoding.Effect.BitReader
1212
import Juvix.Compiler.Nockma.Encoding.Effect.BitWriter
1313
import Juvix.Compiler.Nockma.Language
1414
import Juvix.Prelude.Base
15+
import Juvix.Prelude.Bytes
1516

1617
-- | Encode an atom to little-endian bytes
1718
atomToByteString :: (NockNatural a, Member (Error (ErrNockNatural a)) r) => Atom a -> Sem r ByteString
@@ -30,9 +31,6 @@ byteStringToAtom = fmap mkEmptyAtom . fromNatural . byteStringToNatural
3031
byteStringToNatural :: ByteString -> Natural
3132
byteStringToNatural = fromInteger . byteStringToIntegerLE
3233

33-
naturalToByteString :: Natural -> ByteString
34-
naturalToByteString = naturalToByteStringLE
35-
3634
naturalToBase64 :: Natural -> Text
3735
naturalToBase64 = decodeUtf8 . Base64.encode . naturalToByteString
3836

@@ -58,18 +56,6 @@ byteStringToIntegerLEChunked = foldr' go 0 . map (first byteStringChunkToInteger
5856
byteStringChunkToInteger :: ByteString -> Integer
5957
byteStringChunkToInteger = BS.foldr' (\b acc -> acc `shiftL` 8 .|. fromIntegral b) 0
6058

61-
-- | TODO: this is quadratic (`shiftR` is O(n))
62-
naturalToByteStringLE :: Natural -> ByteString
63-
naturalToByteStringLE = BS.toStrict . BS.toLazyByteString . go
64-
where
65-
go :: Natural -> BS.Builder
66-
go = \case
67-
0 -> mempty
68-
n -> BS.word8 (fromIntegral n) <> go (n `shiftR` 8)
69-
70-
naturalToByteStringLELen :: Int -> Natural -> ByteString
71-
naturalToByteStringLELen len = padByteString len . naturalToByteStringLE
72-
7359
textToNatural :: Text -> Natural
7460
textToNatural = byteStringToNatural . encodeUtf8
7561

@@ -90,12 +76,6 @@ mkEmptyAtom x =
9076
_atom = x
9177
}
9278

93-
-- | Pad a ByteString with zeros up to a specified length
94-
padByteString :: Int -> ByteString -> ByteString
95-
padByteString n bs
96-
| BS.length bs >= n = bs
97-
| otherwise = BS.append bs (BS.replicate (n - BS.length bs) 0)
98-
9979
vectorBitsToInteger :: Bit.Vector Bit -> Integer
10080
vectorBitsToInteger = byteStringToIntegerLEChunked . vectorBitsToByteString
10181

src/Juvix/Prelude.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Juvix.Prelude
22
( module Juvix.Prelude.Base,
33
module Juvix.Prelude.Lens,
44
module Juvix.Prelude.Generic,
5+
module Juvix.Prelude.Bytes,
56
module Juvix.Prelude.Trace,
67
module Juvix.Prelude.Path,
78
module Juvix.Prelude.Prepath,
@@ -11,6 +12,7 @@ where
1112

1213
import Juvix.Data
1314
import Juvix.Prelude.Base
15+
import Juvix.Prelude.Bytes
1416
import Juvix.Prelude.Generic
1517
import Juvix.Prelude.Lens
1618
import Juvix.Prelude.Path

src/Juvix/Prelude/Base/Foundation.hs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE MagicHash #-}
12
{-# OPTIONS_GHC -Wno-orphans #-}
23
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
34

@@ -71,6 +72,7 @@ module Juvix.Prelude.Base.Foundation
7172
module Text.Read,
7273
module Text.Show,
7374
module Text.Show.Unicode,
75+
module Data.Bits,
7476
Data,
7577
Text,
7678
pack,
@@ -132,6 +134,7 @@ import Control.Monad.Zip
132134
import Data.Array qualified as Array
133135
import Data.Bifunctor hiding (first, second)
134136
import Data.Bitraversable
137+
import Data.Bits hiding (And, shift)
135138
import Data.Bool
136139
import Data.ByteString (ByteString)
137140
import Data.Char
@@ -178,6 +181,7 @@ import Data.Map.Strict (Map)
178181
import Data.Maybe
179182
import Data.Monoid
180183
import Data.Ord
184+
import Data.Primitive.ByteArray qualified as GHCByteArray
181185
import Data.Semigroup (Semigroup, sconcat, (<>))
182186
import Data.Serialize (Serialize)
183187
import Data.Serialize as Serial
@@ -264,6 +268,8 @@ type TextBuilder = LazyText.Builder
264268

265269
type GHCType = GHC.Type
266270

271+
type GHCByteArray = GHCByteArray.ByteArray
272+
267273
type GHCConstraint = GHC.Constraint
268274

269275
type LazyHashMap = LazyHashMap.HashMap
@@ -328,6 +334,9 @@ prime name = case Text.splitOn "'" name of
328334
[name', num] -> name' <> "'" <> maybe (num <> "'") (show . (+ 1)) (Text.readMaybe (unpack num) :: Maybe Word)
329335
_ -> name <> "'"
330336

337+
divisibleBy :: (Integral a) => a -> a -> Bool
338+
divisibleBy a b = a `mod` b == 0
339+
331340
freshName :: HashSet Text -> Text -> Text
332341
freshName names name | HashSet.member name names = freshName names (prime name)
333342
freshName _ name = name

src/Juvix/Prelude/Bytes.hs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{-# LANGUAGE MagicHash #-}
2+
3+
module Juvix.Prelude.Bytes where
4+
5+
import Data.ByteString qualified as BS
6+
import Data.ByteString.Builder qualified as BS
7+
import Data.Primitive.ByteArray qualified as GHCByteArray
8+
import Data.Primitive.Types
9+
import GHC.Exts (Word (W#))
10+
import Juvix.Prelude.Base.Foundation
11+
12+
byteArrayToList :: (Prim a) => GHCByteArray -> [a]
13+
byteArrayToList = GHCByteArray.foldrByteArray (:) mempty
14+
15+
byteArrayToBytes :: GHCByteArray -> [Word8]
16+
byteArrayToBytes = byteArrayToList
17+
18+
-- | It assumes bytesPerWord == 8
19+
naturalToWord64 :: Natural -> [Word64]
20+
naturalToWord64 = \case
21+
NS w -> [fromIntegral (W# w)]
22+
NB b -> byteArrayToList (GHCByteArray.ByteArray b)
23+
24+
naturalToBytes :: Natural -> [Word8]
25+
naturalToBytes = \case
26+
NS w -> wordToBytes (W# w)
27+
NB b -> byteArrayToBytes (GHCByteArray.ByteArray b)
28+
29+
-- | Pad a ByteString with zeros up to the smallest length such that is
30+
-- divisible by the given arg
31+
padByteStringMod :: Int -> ByteString -> ByteString
32+
padByteStringMod align bs =
33+
let (d, m) = divMod (BS.length bs) align
34+
in if
35+
| m == 0 -> bs
36+
| otherwise -> padByteString ((d + 1) * align) bs
37+
38+
padByteString8 :: ByteString -> ByteString
39+
padByteString8 = padByteStringMod 8
40+
41+
-- | Pad a ByteString with zeros up to a specified length
42+
padByteString :: Int -> ByteString -> ByteString
43+
padByteString n bs
44+
| BS.length bs >= n = bs
45+
| otherwise = BS.append bs (BS.replicate (n - BS.length bs) 0)
46+
47+
naturalToByteStringLELen :: Int -> Natural -> ByteString
48+
naturalToByteStringLELen len = padByteString len . naturalToByteStringLE
49+
50+
naturalToByteStringOld :: Natural -> ByteString
51+
naturalToByteStringOld = naturalToByteStringLE
52+
53+
-- | TODO: this is quadratic (`shiftR` is O(n))
54+
naturalToByteStringLE :: Natural -> ByteString
55+
naturalToByteStringLE = BS.toStrict . BS.toLazyByteString . go
56+
where
57+
go :: Natural -> BS.Builder
58+
go = \case
59+
0 -> mempty
60+
n -> BS.word8 (fromIntegral n) <> go (n `shiftR` 8)
61+
62+
-- | Little endian
63+
naturalToByteStringTest :: Bool -> Natural -> ByteString
64+
naturalToByteStringTest word64 n
65+
-- most common case
66+
| word64 && 8 == bytesPerWord =
67+
let w :: [Word64] = naturalToWord64 n
68+
in build (mconcat (map BS.word64LE w))
69+
| otherwise = BS.pack (naturalToBytes n)
70+
where
71+
build :: BS.Builder -> ByteString
72+
build = BS.toStrict . BS.toLazyByteString
73+
74+
naturalToByteString :: Natural -> ByteString
75+
naturalToByteString = naturalToByteStringTest True
76+
77+
-- | Little endian
78+
wordToBytes :: Word -> [Word8]
79+
wordToBytes w = [fromIntegral (w `shiftR` (i * 8)) | i <- [0 .. bytesPerWord - 1]]
80+
81+
-- | Platform dependent
82+
bytesPerWord :: Int
83+
bytesPerWord = sizeOf (impossible :: Word)

0 commit comments

Comments
 (0)