|
| 1 | +{-# LANGUAGE ImportQualifiedPost #-} |
| 2 | +{-# LANGUAGE LambdaCase #-} |
| 3 | + |
| 4 | +-- | This module contains benchmarks for Peras chain weight calculation as implemented by |
| 5 | +-- the by the 'Ouroboros.Consensus.Storage.PerasCertDB.API.boostedWeightForFragment' |
| 6 | +-- function. |
| 7 | +-- |
| 8 | +-- We benchmark the calculation on a static sequence of chain fragments of increasing |
| 9 | +-- length, ranging from 0 to around 8640, with a sampling rate of 100. The chain fragments |
| 10 | +-- are instantiated with 'TestBlock', and every 5 blocks there is a booster block with |
| 11 | +-- weight 15. All parameters are set in 'benchmarkParams'. |
| 12 | +module Main (main) where |
| 13 | + |
| 14 | +import Data.List (iterate') |
| 15 | +import Data.Map.Strict qualified as Map |
| 16 | +import Numeric.Natural (Natural) |
| 17 | +import Ouroboros.Consensus.Block (PerasWeight (PerasWeight), SlotNo (..)) |
| 18 | +import Ouroboros.Consensus.Storage.PerasCertDB.API |
| 19 | + ( PerasWeightSnapshot (..) |
| 20 | + , boostedWeightForFragment |
| 21 | + ) |
| 22 | +import Ouroboros.Network.AnchoredFragment qualified as AF |
| 23 | +import Test.Ouroboros.Storage.TestBlock (TestBlock (..), TestBody (..), TestHeader (..)) |
| 24 | +import Test.Ouroboros.Storage.TestBlock qualified as TestBlock |
| 25 | +import Test.Tasty.Bench |
| 26 | + |
| 27 | +data BenchmarkParams = BenchmarkParams |
| 28 | + { blockRate :: SlotNo |
| 29 | + -- ^ How often the fragments will contain blocks, in slots |
| 30 | + , fragmentLenghtSamplingRate :: Natural |
| 31 | + -- ^ The rate of length increase for generate chain fragments |
| 32 | + , fragmentMaxLenght :: Natural |
| 33 | + -- ^ the maximum length of a fragment |
| 34 | + , boostedBlockRate :: Natural |
| 35 | + -- ^ How often boosted blocks occur, in blocks |
| 36 | + , boostWeight :: PerasWeight |
| 37 | + -- ^ The weight of the boost |
| 38 | + } |
| 39 | + |
| 40 | +benchmarkParams :: BenchmarkParams |
| 41 | +benchmarkParams = |
| 42 | + BenchmarkParams |
| 43 | + { blockRate = 20 |
| 44 | + , fragmentLenghtSamplingRate = 100 |
| 45 | + , fragmentMaxLenght = 2160 + 3 * 2160 |
| 46 | + , boostedBlockRate = 5 |
| 47 | + , boostWeight = PerasWeight 15 |
| 48 | + } |
| 49 | + |
| 50 | +main :: IO () |
| 51 | +main = |
| 52 | + Test.Tasty.Bench.defaultMain $ map benchBoostedWeightForFragment inputs |
| 53 | + where |
| 54 | + -- NOTE: we do not use the 'env' combinator to set up the test data since |
| 55 | + -- it requires 'NFData' for 'AF.AnchoredFragment'. While the necessary |
| 56 | + -- instances could be provided, we do not think is necessary for this |
| 57 | + -- benchmark, as the input data is rather small. |
| 58 | + inputs :: [(Natural, (PerasWeightSnapshot TestBlock, AF.AnchoredFragment TestBlock))] |
| 59 | + inputs = |
| 60 | + getEveryN (fragmentLenghtSamplingRate benchmarkParams) $ |
| 61 | + take (fromIntegral $ fragmentMaxLenght benchmarkParams) $ |
| 62 | + zip [0 ..] $ |
| 63 | + zip (map uniformWeightSnapshot fragments) fragments |
| 64 | + |
| 65 | +benchBoostedWeightForFragment :: |
| 66 | + (Natural, (PerasWeightSnapshot TestBlock, AF.AnchoredFragment TestBlock)) -> Benchmark |
| 67 | +benchBoostedWeightForFragment (i, (weightSnapshot, fragment)) = |
| 68 | + bench ("boostedWeightForFragment of length " <> show i) $ |
| 69 | + whnf (boostedWeightForFragment weightSnapshot) fragment |
| 70 | + |
| 71 | +-- | An infinite list of chain fragments |
| 72 | +fragments :: [AF.AnchoredFragment TestBlock] |
| 73 | +fragments = iterate' addSuccessorBlock genesisFragment |
| 74 | + where |
| 75 | + genesisFragment :: AF.AnchoredFragment TestBlock |
| 76 | + genesisFragment = AF.Empty AF.AnchorGenesis |
| 77 | + |
| 78 | + addSuccessorBlock :: AF.AnchoredFragment TestBlock -> AF.AnchoredFragment TestBlock |
| 79 | + addSuccessorBlock = \case |
| 80 | + AF.Empty _ -> (AF.Empty AF.AnchorGenesis) AF.:> (TestBlock.firstBlock 0 dummyBody) |
| 81 | + (xs AF.:> x) -> |
| 82 | + let nextBlockSlot = blockRate benchmarkParams + (thSlotNo . testHeader $ x) |
| 83 | + in (xs AF.:> x) AF.:> TestBlock.mkNextBlock x nextBlockSlot dummyBody |
| 84 | + |
| 85 | + dummyBody :: TestBody |
| 86 | + dummyBody = TestBody{tbForkNo = 0, tbIsValid = True} |
| 87 | + |
| 88 | +-- | Given a chain fragment, construct a weight snapshot where there's a boosted block every 90 slots |
| 89 | +uniformWeightSnapshot :: AF.AnchoredFragment TestBlock -> PerasWeightSnapshot TestBlock |
| 90 | +uniformWeightSnapshot fragment = |
| 91 | + let pointsToBoost = |
| 92 | + map snd |
| 93 | + . getEveryN (boostedBlockRate benchmarkParams) |
| 94 | + . zip [0 ..] |
| 95 | + . map AF.blockPoint |
| 96 | + . AF.toOldestFirst |
| 97 | + $ fragment |
| 98 | + weights = repeat (boostWeight benchmarkParams) |
| 99 | + in PerasWeightSnapshot{getPerasWeightSnapshot = Map.fromList $ zip pointsToBoost weights} |
| 100 | + |
| 101 | +getEveryN :: Natural -> [(Natural, a)] -> [(Natural, a)] |
| 102 | +getEveryN n = filter (\(i, _) -> (i `mod` n) == 0) |
0 commit comments