Experiment: && vs builtinAnd vs alternatives — boolean AND chaining budget#7583
Draft
Experiment: && vs builtinAnd vs alternatives — boolean AND chaining budget#7583
Conversation
…udget Compare 4 patterns for chaining boolean conditions in Plutus Tx: 1. Standard && (lazy, delay/force) 2. builtinAnd (lambda/unit, Philip DiSarro's pattern from PR #7562) 3. Multi-way if (negated guards) 4. Direct BI.ifThenElse chain (manual lambda/unit) Each pattern tested with 3 scenarios: AllTrue, EarlyFail, LateFail.
Contributor
Extract patterns 2 and 4 into Budget.BuiltinAndLib with the exact GHC flags used in PR #7562's ValidatorOptimized.hs: - All -fno-* optimisation flags - conservative-optimisation plugin option - INLINE pragmas (not INLINEABLE) Results unchanged: builtinAnd still doesn't short-circuit (839,970 CPU in all scenarios) because Bool arguments are evaluated eagerly before the function body runs, regardless of INLINE/flags.
… experiment Add two more patterns to the && vs builtinAnd comparison: Pattern 5 (andBuiltinAndIfError): Philip's exact claimed usage - if (builtinAnd a $ builtinAnd b c) then ok else error Result: 919,970 CPU, no short-circuiting (all conditions evaluated) Pattern 6 (andBuiltinIfNest): Nested builtinIf with conditions in lambda bodies (Philip's actual validator pattern from PR #7562) Result: 727,970 CPU AllTrue, with proper short-circuiting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Not for merge — this is an experiment to share findings with colleagues. Related to PR #7562.
Context
Philip DiSarro's PR #7562 introduces
builtinAndandbuiltinIfhelpers as alternatives to standard&&for boolean condition chaining in Plutus validators. The claim is thatbuiltinAnd(usingBI.ifThenElse+ lambda/unit) avoidsdelay/forceoverhead and is cheaper than&&.This experiment measures the actual execution budget across 6 patterns with 3 test scenarios each (18 golden tests total).
Patterns tested
Patterns returning
Bool(1–4)&&— lazy, plugin-rewritten todelay/forcebuiltinAnd— Philip's helper usingBI.ifThenElse+ lambda/unitBI.ifThenElse— fully hand-written lambda/unit chainPatterns returning
BuiltinUnitwithtraceErroron failure (5–6)builtinAnd+ if/error — Philip's exact claimed usage:if (builtinAnd a $ builtinAnd b c) then ok else errorbuiltinIf— Philip's actual validator pattern from PR feat: native builtin data vs th builtin data benchmark #7562Fairness
Patterns 2, 4, 5, 6 are compiled in a separate module (
BuiltinAndLib.hs) with Philip's exact GHC flags fromValidatorOptimized.hs:-fno-full-laziness,-fno-strictness,-fno-specialise,-fno-spec-constr-fno-ignore-interface-pragmas,-fno-omit-interface-pragmas-fno-unbox-small-strict-fields,-fno-unbox-strict-fields-fplugin-opt PlutusTx.Plugin:conservative-optimisation-fplugin-opt PlutusTx.Plugin:datatypes=BuiltinCasing{-# INLINE #-}pragmas (notINLINEABLE)Results
Patterns 1–4 (return
Bool)&&(lazy)builtinAndBI.ifThenElsePatterns 5–6 (return
BuiltinUnit,traceErroron failure)builtinAnd+ if/errorbuiltinIf(Fail cases produce
traceError, so no budget is reported — this is the intended validator behavior.)Key findings
builtinAnddoes not short-circuit.builtinAnd :: Bool -> Bool -> Booltakes strict arguments — bothBoolvalues are computed before the function body runs. All 3 scenarios produce identical budgets (839,970 CPU). This holds even with Philip's exact GHC flags andINLINEpragmas.builtinAndis the most expensive pattern. It costs 52% more CPU than standard&&(839,970 vs 551,970) in the AllTrue case, and 178% more in the EarlyFail case (839,970 vs 301,390).Standard
&&is the cheapest pattern overall. It properly short-circuits and has the lowest CPU cost in all scenarios.Nested
builtinIfworks (pattern 6). When conditions are placed inside lambda bodies, short-circuiting is preserved. This is what Philip's actual validator code in PR feat: native builtin data vs th builtin data benchmark #7562 does — but it's a different pattern frombuiltinAnd.The UPLC confirms it. Pattern 2 compiles all
lessThanIntegercalls into let-bindings before anycaseexpression. Pattern 6 nestslessThanIntegerinside lambda bodies.UPLC comparison
Pattern 2 (
builtinAnd) — no short-circuiting:Pattern 6 (nested
builtinIf) — proper short-circuiting: