Skip to content

Cost model for keepPolicies (CIP-0168) - #7930

Closed
Unisay wants to merge 3 commits into
masterfrom
yura/issue-2310-keep-policies-costing
Closed

Unisay wants to merge 3 commits into
masterfrom
yura/issue-2310-keep-policies-costing

Conversation

@Unisay

@Unisay Unisay commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

Adds the cost model for keepPolicies, five parameters: CPU linear in the length of the policy list and in Value.totalSize, memory linear in Value.totalSize. The denotation takes the plain Value, because instance ExMemoryUsage Value already reports totalSize. ValueOuterSize is not the measure here even though keepPolicies looks like policies from the outside: it reports the policy count, so a Value of one policy holding 10000 tokens would be measured as size 1 while pack' folds all 10000 pairs.

Memory is six words per new outer-map node, at most one per pair in the result. The result shares the inner maps wholesale and the policy ids by pointer, so the outer spine is the only new structure.

Implements IntersectMBO/plutus-private#2310.

@Unisay Unisay self-assigned this Sep 1, 2026
@Unisay
Unisay changed the base branch from master to yura/issue-2306-keep-drop-policies September 1, 2026 12:37
@Unisay
Unisay force-pushed the yura/issue-2310-keep-policies-costing branch from b7c3892 to ae4a298 Compare September 2, 2026 08:44
@github-actions

github-actions Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.6.3

🚀 View preview at
https://IntersectMBO.github.io/plutus/pr-preview/cost-models/pr-7930/

Built to branch gh-pages at 2026-09-07 14:32 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@Unisay
Unisay force-pushed the yura/issue-2310-keep-policies-costing branch from ae4a298 to e491998 Compare September 2, 2026 09:15
@Unisay
Unisay force-pushed the yura/issue-2310-keep-policies-costing branch from e491998 to 3dc9dba Compare September 2, 2026 10:10
Base automatically changed from yura/issue-2306-keep-drop-policies to master September 3, 2026 11:57
@Unisay
Unisay force-pushed the yura/issue-2310-keep-policies-costing branch from 3dc9dba to b181995 Compare September 3, 2026 11:57
@Unisay
Unisay force-pushed the yura/issue-2310-keep-policies-costing branch from b181995 to ec470fc Compare September 7, 2026 11:50
Every point of the main sweep is the costliest input for its pair of sizes:
the builtin skips the part of the outer map the list cannot reach, so the
pairs that cost anything are the pairs the list names, and packing them
into as few policies as the list covers puts all of them in reach.

The list length and the total size are sampled log-uniformly and
independently, and two sweeps hold both fixed while varying what a cost model
cannot see: the policy count and how much of the list hits. Both move the
cost, so no model of the two sizes is right everywhere, and these sweeps
are what measures how far wrong it gets. The shape sweep runs against a
short list as well as a long one, because the list term swamps the `Value`
term once the list is long.

The points with an empty `Value` double as a control on the rig: the builtin
returns before it looks at the list there, so the same list costs about a
hundredth of what it costs against a `Value` that is not empty. A benchmark
that floated the list work out of the measured loop would show no such gap.

Shares the log-uniform sampler and the `Value` generator with the `policies`
benchmark, which had its own copies of both.
Linear in the length of the policy list and in `Value.totalSize`, which
is what `instance ExMemoryUsage Value` already reports, so the denotation
needs no size wrapper. Five parameters, fitted against semantics variant E,
which is what PlutusV3 and PlutusV4 map to.

The fit undercharges 8 of its 126 points, at worst by 1.28x and in seven of
the eight by between 1.01x and 1.18x. That is the file's normal condition:
scored the same way against their own rows, the shipped `addInteger`
undercharges 48% of its 225 rows, `appendByteString` 59% of 441 and
`valueContains` 49% of 1158, none by more than 1.2x.

Points with an empty outer map are left out of the fit, because the builtin
returns before it looks at the list and an element costs single-digit
nanoseconds there against the hundreds the real work costs. Scoring on
only the 120 rows the fit sees gives 7 undercharges rather than 8.

Memory is six words per new outer-map node, at most one per pair in the
result: the result shares the inner maps wholesale and the policy ids by
pointer, so the outer spine is the only new structure.

Implements IntersectMBO/plutus-private#2310.
One 3D plot: list length against `Value.totalSize` against time, with
the measured points in blue and the model's charge for the same inputs
in red. The spread at a fixed pair of sizes is what no model of those two
numbers can capture, and in three dimensions it reads straight off the plot.

The page computes a least-squares fit from the CSV it has already loaded and
reports it beside the shipped coefficients, so the figures follow whatever
data the page is pointed at rather than the JSON alone. `fitLinearInXAndY`
in the shared code does that and reproduces what `models.R` gets from the
same rows.

The plot panel is created in `renderPlots` rather than declared in the
page, because the shared loader replaces the contents of `#plot-container`
with its own status message before it calls the page's render. Every other
page targets that container itself, so nothing there noticed.
@Unisay
Unisay requested a review from kwxm September 7, 2026 14:31
@Unisay
Unisay marked this pull request as ready for review September 7, 2026 14:31
@Unisay
Unisay force-pushed the yura/issue-2310-keep-policies-costing branch from ec470fc to c83cd63 Compare September 7, 2026 14:32
@Unisay
Unisay marked this pull request as draft September 10, 2026 14:30
@Unisay
Unisay removed the request for review from kwxm September 11, 2026 09:12

@kwxm kwxm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started reviewing this a few days ago, but I think it's going to change so I'll come back and review it properly later. In the meantime, here's a couple of (not very important) comments from my partial review.

## policy-filter builtins, which return before they look at the list when the `Value` is
## empty, so the y = 0 points cost the machine sizing the list rather than the builtin
## doing its work, and one slope cannot price both.
linearInXAndYNonzeroY <- function (fname) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this doesn't need to be in a function of its own: the cost model for keepPolicies could just include this verbatim since it's the only buitlin that uses it. Currently there are generic functions for fitting models which are constant or linear in one variable, but I think that for two-variable models each builtin has its own function that does all of the work and we haven't tried to factor out any commonalities. That's possibly bad practice, but I think it's happened because we've added the fitting functions as we've added new builtins and the models have all tended to be slightly different, so there wasn't much to be gained by abstraction. Maybe we should look again and see if there's any code that could be shared. Looking at the code for fitting the multi-variable models, many of them filter out things of size 0; maybe we should just do this for everything because the zero-sized inputs tend to correspond to special cases that may be faster than the typical case (like appending an empty bytestring, where no work is required), so they may drag the model for typical inputs down a bit.

}
}

/* The two argument sizes on the floor, time up. Every benchmark point appears twice at the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the floor? The x-y plane at the bottom? "Every benchmark point appears twice at the same place on the floor" makes it sound as if those points are actually on the plane, not above it.

@Unisay

Unisay commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #7954, which collapses this stack into a single PR with a rebuilt history: the cost model, the benchmark generator and the Notes introduced here were all replaced further up the stack. Kenneth's two comments are addressed there — the zero-sized rows are dropped from the fit in the shared multipliedSizesFan, and the plot description no longer talks about a floor.

@Unisay Unisay closed this Sep 15, 2026

This branch was previously deployed

1 inactive deployment
github-pages — c83cd636 Deployed Sep 7, 2026 by Unisay via Deploy #122
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants