Use UArray instead of Set for builtin availability check#7736
Use UArray instead of Set for builtin availability check#7736zeme-wana wants to merge 3 commits into
Conversation
The builtin availability check in `scriptCBORDecoder` was using `Set DefaultFun` for O(log n) membership, with a TODO suggesting `IntSet`. A `UArray DefaultFun Bool` is a better fit: since `DefaultFun` derives `Ix`, lookup is a true O(1) unboxed array index with no conversion at the lookup site. The array is built once per call via `runSTUArray`, folding over the `Set` through its `Foldable` instance (no intermediate list). The array covers all ~100 `DefaultFun` constructors.
|
Unclear this is an improvement. Try |
|
/benchmark validation-decode |
|
/benchmark validation-full |
|
Click here to check the status of your benchmark. |
|
Comparing benchmark results of 'validation-decode' on '0468c1c57d' (base) and 'f2742c451c' (PR) Results table
|
|
Click here to check the status of your benchmark. |
Unisay
left a comment
There was a problem hiding this comment.
Nice cleanup on the TODO.
Worth noting that the lookup is O(1) now, but the array itself gets rebuilt on every scriptCBORDecoder call. (ll, pv) ranges over a small finite set (four ledger languages times the handful of known protocol versions), so caching the array at module level costs almost nothing. Each decode then does a Map lookup instead of newArray plus a mapM_ over the Set.
Roughly:
availableBuiltinsArr
:: PlutusLedgerLanguage -> MajorProtocolVersion -> UArray DefaultFun Bool
availableBuiltinsArr =
let cache = Map.fromList
[((ll, pv), build ll pv) | ll <- [minBound .. maxBound], pv <- knownPVs]
build ll pv = runSTUArray $ do
arr <- newArray (minBound, maxBound) False
mapM_ (\f -> writeArray arr f True) (builtinsAvailableIn ll pv)
pure arr
in \ll pv -> Map.findWithDefault emptyArr (ll, pv) cacheMajorProtocolVersion doesn't have a meaningful Bounded instance, so knownPVs has to be listed by hand from Common/Versions.hs: alonzoPV, vasilPV, changPV, plominPV, vanRossemPV.
Whichever variant lands, it would be nice to see the win show up in plutus-benchmark:validation numbers before merging.
|
Comparing benchmark results of 'validation-full' on '0468c1c57d' (base) and 'f2742c451c' (PR) Results table
|
|
I'll defer this to @kwxm |
|
/benchmark validation-decode |
|
Click here to check the status of your benchmark. |
|
/benchmark validation-decode |
1 similar comment
|
/benchmark validation-decode |
|
Comparing benchmark results of 'validation-decode' on '778504b86f' (base) and '7069dba7a4' (PR) Results table
|
|
Click here to check the status of your benchmark. |
|
Comparing benchmark results of 'validation-decode' on '778504b86f' (base) and '7069dba7a4' (PR) Results table
|
|
Click here to check the status of your benchmark. |
|
Comparing benchmark results of 'validation-decode' on '778504b86f' (base) and '7069dba7a4' (PR) Results table
|
kwxm
left a comment
There was a problem hiding this comment.
This looks promising, but I think it could be made more efficient. Also the decoding benchmark results are a bit strange: on average this seems to speed things up a bit, but if you you at the results for individual scripts it looks as if it gets quicker to decode some scripts and slower to decode others. Maybe that's just random though: the speedups/slowdowns don't seem to be consistent over different benchmark runs. It could just be Criterion being weird.
Relatedly, I have an issue to look again at the whole builtinsAvailableIn thing. It may be possbile to compute a lot more of this stuff at compile time, but I'm not totally confident about that. I did some experiments in #7232 , #7233, and #7241 but then had to go and do other stuff. I'll take another look some time.
| let availableBuiltins = builtinsAvailableIn ll pv | ||
| let available = builtinsAvailableIn ll pv | ||
|
|
||
| availableArr :: UArray DefaultFun Bool |
There was a problem hiding this comment.
I think that this is the only place (except maybe in tests) that builtinsAvailableIn is used, so you could try making that and builtinsIntroducedIn return a UArray instead of a set. That might be more efficient than introducing the set only to get rid of it again later. For consistency you'd probably need to do something similar for some of the other functions in that function.
|
/benchmark validation-decode |
|
/benchmark validation-full |
|
/benchmark validation-decode |
|
Click here to check the status of your benchmark. |
|
/benchmark validation-full |
|
Comparing benchmark results of 'validation-decode' on '778504b86f' (base) and 'fce40f3855' (PR) Results table
|
|
Click here to check the status of your benchmark. |
|
/benchmark validation-decode |
|
Comparing benchmark results of 'validation-full' on '778504b86f' (base) and 'fce40f3855' (PR) Results table
|
|
Click here to check the status of your benchmark. |
|
Comparing benchmark results of 'validation-decode' on '778504b86f' (base) and 'fce40f3855' (PR) Results table
|
|
/benchmark validation-decode |
|
Click here to check the status of your benchmark. |
|
Comparing benchmark results of 'validation-decode' on '778504b86f' (base) and 'fce40f3855' (PR) Results table
|
|
/benchmark validation-decode |
|
Click here to check the status of your benchmark. |
|
Comparing benchmark results of 'validation-decode' on '778504b86f' (base) and 'fce40f3855' (PR) Results table
|

Summary
Addresses the
TODOinSerialisedScript.hs:215suggesting a better data structure for the builtin availability check inscriptCBORDecoder.Previously
checkBuiltindid an O(log n)Set.memberon aSet DefaultFun.DefaultFunalready derivesIx, so aUArray DefaultFun Boolgives a true O(1) unboxed array index with nofromEnum/hash at the lookup site (GHC'sIxinstance handles the offset).