Skip to content

Optimal non-builtin valueOf in plutus-ledger-api Data.Value#7797

Merged
Unisay merged 1 commit into
masterfrom
yura/issue-2242-optimal-valueof
Jun 2, 2026
Merged

Optimal non-builtin valueOf in plutus-ledger-api Data.Value#7797
Unisay merged 1 commit into
masterfrom
yura/issue-2242-optimal-valueof

Conversation

@Unisay

@Unisay Unisay commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

PlutusLedgerApi.V1.Data.Value.valueOf now walks the outer and inner BuiltinLists directly: unsafeDataAsMap for the maps, unsafeDataAsB for the keys, unsafeDataAsI for the result, equalsByteString to compare. The loop returns 0 from the nil-case, so there is no Maybe constructed and immediately scrutinised. Same semantics, no public API change.

Closes plutus-private#2242.

Evidence

The lookup matrix and the unsafeDataAsValue baseline are on a separate, never-merge PR (#7798) so the 96 golden files do not sit in this PR's CI surface. Those goldens regenerate on any upstream plugin or cost-model shift, which makes them noise for a regression gate.

Old vs new valueOf, CPU in thousands, GHC 9.6, plutus-ledger-api 1.65.0.0. Old = master (Map.lookup + withCurrencySymbol continuation). New = this PR.

Shape legend: S1 = ada only (1 token); S3 = ada plus 2 single-token policies (3 tokens); S8 = ada plus 7 single-token policies (8 tokens); S100 = ada plus 10 policies of 10 tokens each (101 tokens). Lookup position is the row index of the matching (currency, token) inside the outer/inner maps: ada = first, middle = around the centre, last = final entry, miss = absent key (full scan).

shape lookup at old CPU new CPU speedup
S1 ada 5 423 3 772 1.44×
S1 miss 3 180 2 500 1.27×
S3 ada 5 423 3 772 1.44×
S3 middle 7 905 5 010 1.58×
S3 last 10 065 6 247 1.61×
S3 miss 7 500 4 974 1.51×
S8 ada 5 423 3 772 1.44×
S8 middle 14 385 8 721 1.65×
S8 last 20 865 12 432 1.68×
S8 miss 18 301 11 159 1.64×
S100 ada 5 423 3 772 1.44×
S100 middle 25 186 15 162 1.66×
S100 last 46 787 27 852 1.68×
S100 miss 24 781 14 870 1.67×

First-position lookups (ada) are constant in both versions because both short-circuit on the first key match; the new path is 1.44× faster on the constant cost. Linear scans (middle, last, miss) get a 1.6–1.7× speedup that holds across shapes, because the per-step cost dropped from Map.lookup-with-dictionary-conversion to a single equalsByteString plus three raw unsafeDataAs* builtins. Memory dropped roughly in proportion.

For comparison against the builtin path (unsafeDataAsValue + lookupCoin), see #7798; the headline is that the new valueOf beats the builtin path on first-position lookups for large values (S100 ada: 3.77 M vs 22.11 M, ~5.9× cheaper).

This comment was marked as resolved.

@Unisay Unisay force-pushed the yura/issue-2242-optimal-valueof branch 2 times, most recently from ae21ea2 to 0ac70fa Compare May 27, 2026 13:22
@Unisay Unisay marked this pull request as ready for review May 27, 2026 13:28
@Unisay Unisay requested a review from a team May 27, 2026 13:28

@zliu41 zliu41 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good, just one comment on the test.

Comment thread plutus-tx-plugin/test-ledger-api/Spec/Data/Value.hs
@Unisay Unisay force-pushed the yura/issue-2242-optimal-valueof branch from 0ac70fa to cd7c252 Compare May 28, 2026 11:28
@Unisay Unisay requested review from Copilot and zliu41 May 28, 2026 11:30

This comment was marked as resolved.

@Unisay Unisay force-pushed the yura/issue-2242-optimal-valueof branch 2 times, most recently from 8271c9e to 613670c Compare May 28, 2026 11:50
Comment thread plutus-tx-plugin/test-ledger-api/Spec/Data/Value.hs
Comment thread plutus-tx-plugin/test-ledger-api/Spec/Data/Value.hs Outdated
Rewrites `PlutusLedgerApi.V1.Data.Value.valueOf` so the non-builtin lookup
path walks the underlying `BuiltinList` directly via `unsafeDataAsMap` /
`unsafeDataAsB` / `unsafeDataAsI`, compares keys with `equalsByteString`,
and short-circuits on the first match. No `Maybe` is materialised: the
"absent" answer is `0`, returned in-place by the `nilCase` of each
traversal. Avoids `withCurrencySymbol`'s continuation + `Map.lookup`'s
`Maybe`-wrapping, and bypasses the `ToData k`/`UnsafeFromData a` dictionary
work that `AssocMap.lookup` does per element. Semantics preserved.

Adds `Spec.Data.Value.test_valueOf`: a QuickCheck property that compiles
`valueOf` via TH, evaluates it on the CEK machine, and compares the result
against the host-Haskell `valueOf` for the same inputs. Differential test
against the Plinth compiler — any divergence is a compilation bug, not a
semantics bug.

Budget evidence (lookup matrix, `unsafeDataAsValue` baseline) lives on the
companion experimental branch `yura/issue-2242-valueof-evidence`, kept out
of this PR to avoid carrying ~96 golden files that would only ever regenerate
on upstream plugin/cost-model changes.

For IntersectMBO/plutus-private#2242.
@Unisay Unisay force-pushed the yura/issue-2242-optimal-valueof branch from 613670c to b4d1df4 Compare May 29, 2026 10:35
@Unisay Unisay requested a review from zliu41 May 29, 2026 11:13
@Unisay Unisay enabled auto-merge (squash) May 29, 2026 12:26
@Unisay Unisay requested a review from a team May 29, 2026 12:27
@Unisay Unisay merged commit 22779de into master Jun 2, 2026
8 checks passed
@Unisay Unisay deleted the yura/issue-2242-optimal-valueof branch June 2, 2026 10:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants