Skip to content

Commit

Permalink
Update costing for revised version of writeBits
Browse files Browse the repository at this point in the history
  • Loading branch information
kwxm committed Jul 19, 2024
1 parent 794bc8c commit e99a354
Show file tree
Hide file tree
Showing 8 changed files with 49 additions and 39 deletions.
53 changes: 30 additions & 23 deletions plutus-core/cost-model/budgeting-bench/Benchmarks/Bitwise.hs
Original file line number Diff line number Diff line change
Expand Up @@ -127,32 +127,39 @@ benchReadBit =
let xs = makeSample seedA
in createTwoTermBuiltinBenchElementwise ReadBit [] $ pairWith topBitIndex xs

{- Benchmarks show that the time taken by `writeBits` depends mostly on the size
of the list of updates, although it may take a little longer to write bits
with larger indices. We run benchmarks involving increasing numbers of
updates to 1024-byte bytestrings, always writing the highest-indexed bit to
take account of this. We use a fresh bytestring for each set of updates.
{- The `writeBits` function takes a bytestring, a list of positions to write to,
and a list of True/False values to write at those positions. It behaves like
`zip` in that if the two lists are of different lengths, the trailing
elements of the longer list are ignored. Because of this we only run
benchmarks with lists of equal length because in the general case the time
taken will depend only on the length of the smaller list and there's nothing
to be gained by traversing a two-dimensional space of inputs. Moreover,
benchmarks show that the time taken by `writeBits` depends mostly on the
number of updates (and not on the length of the bytestring), although it may
take a little longer to write bits with larger indices. We run benchmarks
involving increasing numbers of updates to 1024-byte bytestrings, always
writing the highest-indexed bit to take account of this. We use a fresh
bytestring for each set of updates.
-}
benchWriteBits :: Benchmark
benchWriteBits =
let fun = WriteBits
size = 128 -- This is equal to length 1024.
xs = makeSizedByteStrings seedA $ take numSamples $ repeat size
l = zip xs [1..numSamples]
-- Given a bytestring s and an integer k, return a pair (s,u) where u is a
-- list of updates which write the highest bit in s 10*k times. Here k
-- will range from 1 to numSamples, which is 150.
mkUpdatesFor (s,k) =
let topIndex = topBitIndex s
updates = take (10*k) $ cycle [(topIndex, False), (topIndex, True)]
in (s, updates)
inputs = fmap mkUpdatesFor l
mkBM x y = benchDefault (showMemoryUsage (ListCostedByLength y)) $ mkApp2 fun [] x y
in bgroup (show fun) $ fmap(\(s,u) -> bgroup (showMemoryUsage s) $ [mkBM s u]) inputs
{- This is like createTwoTermBuiltinBenchElementwise except that the benchmark name contains
the length of the list of updates, not the memory usage. The denotation of WriteBits
in Default.Builtins must wrap its second argument in ListCostedByLength to make sure
that the correct ExMemoryUsage instance is called for costing. -}
let size = 128 -- This is equal to length 1024.
xs = makeSizedByteStrings seedA $ replicate numSamples size
updateCounts = [1..numSamples]
positions = zipWith (\x n -> replicate (10*n) (topBitIndex x)) xs updateCounts
-- Given an integer k, return a list of updates which write a bit 10*k
-- times. Here k will range from 1 to numSamples, which is 150.
mkUpdatesFor k = take (10*k) $ cycle [False, True]
updates = fmap mkUpdatesFor updateCounts
inputs = zip3 xs positions updates
in createThreeTermBuiltinBenchElementwiseWithWrappers
(id, ListCostedByLength, ListCostedByLength)
WriteBits [] inputs
{- This is like createThreeTermBuiltinBenchElementwise except that the benchmark
name contains the length of the list of updates, not the memory usage. The
denotation of WriteBits in Default.Builtins must wrap its second and third
arguments in ListCostedByLength to make sure that the correct ExMemoryUsage
instance is called for costing. -}

{- For small inputs `replicateByte` looks constant-time. For larger inputs it's
linear. We're limiting the output to 8192 bytes (size 1024), so we may as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ builtinMemoryModels = BuiltinCostModelBase
, paramXorByteString = Id $ ModelThreeArgumentsLinearInMaxYZ identityFunction
, paramComplementByteString = Id $ ModelOneArgumentLinearInX identityFunction
, paramReadBit = Id $ ModelTwoArgumentsConstantCost 1
, paramWriteBits = Id $ ModelTwoArgumentsLinearInX identityFunction
, paramWriteBits = Id $ ModelThreeArgumentsLinearInX identityFunction
-- The empty bytestring has memory usage 1, so we add an extra memory unit here to make sure that
-- the memory cost of `replicateByte` is always nonzero. That means that we're charging one unit
-- ore than we perhaps should for nonempty bytestrings, but that's negligible (plus there's some
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ createBuiltinCostModel bmfile rfile = do
paramXorByteString <- getParams readCF3 paramXorByteString
paramComplementByteString <- getParams readCF1 paramComplementByteString
paramReadBit <- getParams readCF2 paramReadBit
paramWriteBits <- getParams readCF2 paramWriteBits
paramWriteBits <- getParams readCF3 paramWriteBits
paramReplicateByte <- getParams readCF2 paramReplicateByte
paramShiftByteString <- getParams readCF2 paramShiftByteString
paramRotateByteString <- getParams readCF2 paramRotateByteString
Expand Down
7 changes: 5 additions & 2 deletions plutus-core/cost-model/data/models.R
Original file line number Diff line number Diff line change
Expand Up @@ -767,8 +767,11 @@ modelFun <- function(path) {
complementByteStringModel <- linearInX ("ComplementByteString")
readBitModel <- constantModel ("ReadBit")
writeBitsModel <- linearInY ("WriteBits")
## ^ The Y value here is the length of the list because we use ListCostedByLength in the
## relevant costing benchmark.
## ^ The Y value here is the length of the list of positions because we use ListCostedByLength
## in the relevant costing benchmark. The time actually depends on the minimum of the lengths
## of the second and third arguments of `writeBits`, but that will be at most Y, so using
## linearInY is conservatively safe. If `writeBits` is used correctly then the lengths of the
## second and third arguments will always be the same anyway.
replicateByteModel <- linearInX ("ReplicateByte")
shiftByteStringModel <- linearInX ("ShiftByteString")
rotateByteStringModel <- linearInX ("RotateByteString")
Expand Down
17 changes: 9 additions & 8 deletions plutus-core/cost-model/test/TestCostModels.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ import Hedgehog.Range qualified as Range
for exact equality of the outputs but instead check that the R result and the
Haskell result agreee to within a factor of 2/100 (two percent).
-}
-- FIXME: with this limit the two-variable quadratic costing functions for the
-- integer division builtins fail to pass the test. We don't run the test in
-- CI, but we should still fix it.
-- FIXME: The two-variable quadratic costing functions for the integer division
-- builtins fail to pass the test because the Haskell version adds a floor under
-- the costing function that isn't there in the R version, so we get different
-- results. For the time being the relevant tests are commented out.

-- | Maximum allowable difference beween R result and Haskell result.
epsilon :: Double
Expand Down Expand Up @@ -319,10 +320,10 @@ main =
[ $(genTest 2 "addInteger") Everywhere
, $(genTest 2 "subtractInteger") Everywhere
, $(genTest 2 "multiplyInteger") Everywhere
, $(genTest 2 "divideInteger") BelowDiagonal
, $(genTest 2 "quotientInteger") BelowDiagonal
, $(genTest 2 "remainderInteger") BelowDiagonal
, $(genTest 2 "modInteger") BelowDiagonal
-- , $(genTest 2 "divideInteger") BelowDiagonal
-- , $(genTest 2 "quotientInteger") BelowDiagonal
-- , $(genTest 2 "remainderInteger") BelowDiagonal
-- , $(genTest 2 "modInteger") BelowDiagonal
, $(genTest 2 "lessThanInteger") Everywhere
, $(genTest 2 "lessThanEqualsInteger") Everywhere
, $(genTest 2 "equalsInteger") Everywhere
Expand Down Expand Up @@ -422,7 +423,7 @@ main =
, $(genTest 3 "xorByteString")
, $(genTest 1 "complementByteString")
, $(genTest 2 "readBit") Everywhere
, $(genTest 2 "writeBits") Everywhere
, $(genTest 3 "writeBits")
, $(genTest 2 "replicateByte") Everywhere
, $(genTest 2 "shiftByteString") Everywhere
, $(genTest 2 "rotateByteString") Everywhere
Expand Down
3 changes: 1 addition & 2 deletions plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1944,11 +1944,10 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where
{-# INLINE writeBitsDenotation #-}
in makeBuiltinMeaning
writeBitsDenotation
(runCostingFunTwoArguments . paramWriteBits)
(runCostingFunThreeArguments . paramWriteBits)
toBuiltinMeaning _semvar ReplicateByte =
let replicateByteDenotation :: NumBytesCostedAsNumWords -> Word8 -> BuiltinResult BS.ByteString
replicateByteDenotation (NumBytesCostedAsNumWords n) w = Bitwise.replicateByte n w
-- FIXME: be careful about the coercion in replicateByte
{-# INLINE replicateByteDenotation #-}
in makeBuiltinMeaning
replicateByteDenotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ data BuiltinCostModelBase f =
, paramXorByteString :: f ModelThreeArguments
, paramComplementByteString :: f ModelOneArgument
, paramReadBit :: f ModelTwoArguments
, paramWriteBits :: f ModelTwoArguments
, paramWriteBits :: f ModelThreeArguments
, paramReplicateByte :: f ModelTwoArguments
, paramShiftByteString :: f ModelTwoArguments
, paramRotateByteString :: f ModelTwoArguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ unitCostBuiltinCostModel = BuiltinCostModelBase
, paramXorByteString = unitCostThreeArguments
, paramComplementByteString = unitCostOneArgument
, paramReadBit = unitCostTwoArguments
, paramWriteBits = unitCostTwoArguments
, paramWriteBits = unitCostThreeArguments
, paramReplicateByte = unitCostTwoArguments
, paramShiftByteString = unitCostTwoArguments
, paramRotateByteString = unitCostTwoArguments
Expand Down

0 comments on commit e99a354

Please sign in to comment.