11{-# LANGUAGE BlockArguments #-}
22{-# LANGUAGE ImportQualifiedPost #-}
33{-# LANGUAGE NumericUnderscores #-}
4+ {-# LANGUAGE TupleSections #-}
45
56module Benchmarks.Values (makeBenchmarks ) where
67
@@ -10,6 +11,7 @@ import Common
1011import Control.Monad (replicateM )
1112import Criterion.Main (Benchmark )
1213import Data.ByteString (ByteString )
14+ import Data.Int (Int64 )
1315import PlutusCore (DefaultFun (LookupCoin , UnValueData , ValueContains , ValueData ))
1416import PlutusCore.Evaluation.Machine.ExMemoryUsage (LogValueOuterOrMaxInner (.. ),
1517 ValueTotalSize (.. ))
@@ -40,7 +42,7 @@ lookupCoinBenchmark gen =
4042 (lookupCoinArgs gen) -- the argument combos to generate benchmarks for
4143
4244lookupCoinArgs :: StdGen -> [(ByteString , ByteString , Value )]
43- lookupCoinArgs gen = runStateGen_ gen $ \ (g :: g ) -> do
45+ lookupCoinArgs gen = runStateGen_ gen \ (g :: g ) -> do
4446 -- Add search keys to common test values
4547 let testValues = generateTestValues gen
4648 commonWithKeys <- mapM (withSearchKeys g . pure ) testValues
@@ -49,17 +51,12 @@ lookupCoinArgs gen = runStateGen_ gen $ \(g :: g) -> do
4951 let valueSizes = [(100 , 10 ), (500 , 20 ), (1_000 , 50 ), (2_000 , 100 )]
5052 additionalTests <-
5153 sequence $
52- concat
53- [ -- Value size tests (number of policies × tokens per policy)
54- [ generateLookupTest g numPolicies tokensPerPolicy
55- | (numPolicies, tokensPerPolicy) <- valueSizes
56- ]
57- , -- Budget-constrained tests (at 30KB limit)
58- [generateBudgetTest g 30_000 ]
59- , -- Additional random tests for parameter spread
60- replicate 50 (generateRandomLookupTest g)
61- ]
62-
54+ -- Value size tests (number of policies × tokens per policy)
55+ [ withSearchKeys g (generateConstrainedValue numPolicies tokensPerPolicy g)
56+ | (numPolicies, tokensPerPolicy) <- valueSizes
57+ ]
58+ -- Additional random tests for parameter spread
59+ <> replicate 100 (withSearchKeys g (generateValue g))
6360 pure $ commonWithKeys ++ additionalTests
6461
6562-- | Add random search keys to a Value (keys may or may not exist in the Value)
@@ -70,32 +67,6 @@ withSearchKeys g genValue = do
7067 key2 <- generateKeyBS g
7168 pure (key1, key2, value)
7269
73- -- | Generate lookup test with specified parameters
74- generateLookupTest
75- :: (StatefulGen g m )
76- => g
77- -> Int -- Number of policies
78- -> Int -- Tokens per policy
79- -> m (ByteString , ByteString , Value )
80- generateLookupTest g numPolicies tokensPerPolicy =
81- withSearchKeys g (generateConstrainedValue numPolicies tokensPerPolicy g)
82-
83- -- | Generate budget-constrained test
84- generateBudgetTest
85- :: (StatefulGen g m )
86- => g
87- -> Int -- Total budget
88- -> m (ByteString , ByteString , Value )
89- generateBudgetTest g budget =
90- withSearchKeys g (generateValueWithBudget budget g)
91-
92- -- | Generate random lookup test with varied parameters for better spread
93- generateRandomLookupTest :: (StatefulGen g m ) => g -> m (ByteString , ByteString , Value )
94- generateRandomLookupTest g = do
95- numPolicies <- uniformRM (1 , 2_000 ) g
96- tokensPerPolicy <- uniformRM (1 , 1_000 ) g
97- withSearchKeys g (generateConstrainedValue numPolicies tokensPerPolicy g)
98-
9970----------------------------------------------------------------------------------------------------
10071-- ValueContains -----------------------------------------------------------------------------------
10172
@@ -109,89 +80,23 @@ valueContainsBenchmark gen =
10980 (valueContainsArgs gen) -- the argument combos to generate benchmarks for
11081
11182valueContainsArgs :: StdGen -> [(Value , Value )]
112- valueContainsArgs gen = runStateGen_ gen \ g -> do
113- let baseValueSizes = [1 , 10 , 100 , 1_000 ]
114- sequence $
115- concat
116- [ -- Value size tests with varying sizes
117- [ generateContainsTest g containerSize containedSize
118- | containerSize <- baseValueSizes
119- , containedSize <- baseValueSizes
120- , containedSize <= containerSize
121- ]
122- , -- Budget-constrained tests
123- [generateContainsBudgetTest g 30_000 ]
124- , -- Edge cases
125- [ generateEmptyContainedTest g containerSize
126- | containerSize <- [0 , 10 , 100 , 1_000 ]
127- ]
128- , -- Random tests for parameter spread (100 combinations)
129- replicate 100 (generateRandomContainsTest g)
130- ]
131-
132- -- | Generate valueContains test with specified parameters
133- generateContainsTest
134- :: (StatefulGen g m )
135- => g
136- -> Int -- Container value size (number of policies)
137- -> Int -- Contained value size (number of policies)
138- -> m (Value , Value )
139- generateContainsTest g containerSize containedSize = do
140- -- Generate container value
141- container <- generateConstrainedValue containerSize 10 g
142-
143- -- Generate contained as subset of container (for true contains relationship)
144- let containerList = Value. toList container
145- containedEntries = take containedSize containerList
146-
83+ valueContainsArgs gen = runStateGen_ gen \ g -> replicateM 100 do
84+ -- Generate a random container value
85+ container <- generateValue g
86+ -- Select a random subset of entries from the container to ensure contained ⊆ container
87+ containedSize <- uniformRM (0 , Value. totalSize container) g
88+ -- Take the first containedSize entries to ensure contained ⊆ container
89+ let selectedEntries = take containedSize (Value. toFlatList container)
90+
91+ -- Group selected entries back by policy
14792 let contained =
148- Value. fromList $
149- [ (policyId, take (containedSize `div` max 1 ( length containerList)) tokens )
150- | (policyId, tokens ) <- containedEntries
93+ Value. fromList
94+ [ (policyId, [(tokenName, quantity)] )
95+ | (policyId, tokenName, quantity ) <- selectedEntries
15196 ]
15297
15398 pure (container, contained)
15499
155- -- | Generate budget-constrained contains test
156- generateContainsBudgetTest
157- :: (StatefulGen g m )
158- => g
159- -> Int -- Total budget
160- -> m (Value , Value )
161- generateContainsBudgetTest g budget = do
162- container <- generateValueWithBudget budget g
163- -- Generate smaller contained value (subset)
164- let containerList = Value. toList container
165- containedEntries = take (length containerList `div` 2 ) containerList
166- pure (container, Value. fromList containedEntries)
167-
168- -- | Generate test with empty contained value
169- generateEmptyContainedTest
170- :: (StatefulGen g m )
171- => g
172- -> Int -- Container size (number of policies)
173- -> m (Value , Value )
174- generateEmptyContainedTest g containerSize = do
175- container <- generateConstrainedValue containerSize 10 g
176- pure (container, Value. empty)
177-
178- -- | Generate random valueContains test with varied parameters for better spread
179- generateRandomContainsTest :: (StatefulGen g m ) => g -> m (Value , Value )
180- generateRandomContainsTest g = do
181- -- Generate random parameters with good spread
182- containerEntries <- uniformRM (1 , 5_000 ) g -- 1-5000 container entries
183- containedEntries <- uniformRM (1 , containerEntries) g -- 1-container count
184-
185- -- Generate container value (1 token per policy for flat structure)
186- container <- generateConstrainedValue containerEntries 1 g
187-
188- -- Generate contained as subset of container entries
189- let containerList = Value. toList container
190- containedList = take containedEntries containerList
191- contained = Value. fromList containedList
192-
193- pure (container, contained)
194-
195100----------------------------------------------------------------------------------------------------
196101-- ValueData ---------------------------------------------------------------------------------------
197102
@@ -210,28 +115,41 @@ unValueDataBenchmark gen =
210115
211116-- | Generate common test values for benchmarking
212117generateTestValues :: StdGen -> [Value ]
213- generateTestValues gen = runStateGen_ gen \ g -> do
214- let
215- baseValueSizes = [1 , 10 , 50 , 100 , 500 , 1_000 ]
216-
217- sequence $
218- concat
219- [ -- Empty value as edge case
220- [pure Value. empty]
221- , -- Standard value sizes
222- [ generateConstrainedValue numPolicies 10 g
223- | numPolicies <- baseValueSizes
224- ]
225- , -- Budget-constrained tests
226- [ generateValueWithBudget budget g
227- | budget <- [1_000 , 10_000 , 30_000 ]
228- ]
229- , -- Random tests for parameter spread (50 combinations)
230- replicate 50 $ do
231- numPolicies <- uniformRM (1 , 1_000 ) g
232- tokensPerPolicy <- uniformRM (1 , 500 ) g
233- generateConstrainedValue numPolicies tokensPerPolicy g
234- ]
118+ generateTestValues gen = runStateGen_ gen \ g ->
119+ -- Empty value as edge case
120+ (Value. empty : )
121+ <$>
122+ -- Random tests for parameter spread (100 combinations)
123+ replicateM 100 (generateValue g)
124+
125+ -- | Generate Value with random budget between 1 and 30,000 bytes
126+ generateValue :: (StatefulGen g m ) => g -> m Value
127+ generateValue g = do
128+ maxEntries <- uniformRM (1 , maxValueEntries maxValueInBytes) g
129+ generateValueMaxEntries maxEntries g
130+ where
131+ -- Maximum budget for Value generation (30,000 bytes)
132+ maxValueInBytes :: Int
133+ maxValueInBytes = 30_000
134+
135+ -- Calculate maximum possible number of entries with maximal key sizes that fits in the budget
136+ maxValueEntries :: Int -> Int
137+ maxValueEntries budget =
138+ let bytesPerEntry =
139+ {- bytes per policy -} Value. maxKeyLen
140+ {- bytes per token -} + Value. maxKeyLen
141+ {- bytes per quantity (Int64 takes up 8 bytes) -} + 8
142+ in budget `div` bytesPerEntry
143+
144+ -- | Generate Value within total size budget
145+ generateValueMaxEntries :: (StatefulGen g m ) => Int -> g -> m Value
146+ generateValueMaxEntries maxEntries g = do
147+ -- Uniform random distribution: cover full range from many policies (few tokens each)
148+ -- to few policies (many tokens each)
149+ numPolicies <- uniformRM (1 , maxEntries) g
150+ let tokensPerPolicy = if numPolicies > 0 then maxEntries `div` numPolicies else 0
151+
152+ generateConstrainedValue numPolicies tokensPerPolicy g
235153
236154-- | Generate constrained Value
237155generateConstrainedValue
@@ -244,42 +162,13 @@ generateConstrainedValue numPolicies tokensPerPolicy g = do
244162 policyIds <- replicateM numPolicies (generateKey g)
245163 tokenNames <- replicateM tokensPerPolicy (generateKey g)
246164
247- -- Generate positive quantities (1 to 1000000)
248- let quantity :: Int -> Int -> Integer
249- quantity policyIndex tokenIndex =
250- fromIntegral (1 + (policyIndex * 1_000 + tokenIndex) `mod` 1_000_000 )
165+ let quantity :: Integer
166+ quantity = fromIntegral (maxBound :: Int64 )
251167
252168 nestedMap :: [(K , [(K , Integer )])]
253- nestedMap =
254- [ ( policyId
255- , [ (tokenName, quantity policyIndex tokenIndex)
256- | (tokenIndex, tokenName) <- zip [0 .. ] tokenNames
257- ]
258- )
259- | (policyIndex, policyId) <- zip [0 .. ] policyIds
260- ]
261- pure $ Value. fromList nestedMap
169+ nestedMap = (,(,quantity) <$> tokenNames) <$> policyIds
262170
263- -- | Generate Value within total size budget
264- generateValueWithBudget
265- :: (StatefulGen g m )
266- => Int -- Target total size budget
267- -> g
268- -> m Value
269- generateValueWithBudget budget g = do
270- let
271- keySize = Value. maxKeyLen
272- overhead = 8 -- bytes per amount
273-
274- -- Calculate maximum possible entries with fixed key sizes
275- bytesPerEntry = keySize + keySize + overhead -- policy + token + amount
276- maxEntries = budget `div` bytesPerEntry
277-
278- -- Simple distribution: try to balance policies and tokens
279- numPolicies = max 1 (floor (sqrt (fromIntegral maxEntries :: Double )))
280- tokensPerPolicy = if numPolicies > 0 then maxEntries `div` numPolicies else 0
281-
282- generateConstrainedValue numPolicies tokensPerPolicy g
171+ pure $ Value. fromList nestedMap
283172
284173----------------------------------------------------------------------------------------------------
285174-- Other Generators --------------------------------------------------------------------------------
0 commit comments