diff --git a/doc/cost-models/README.md b/doc/cost-models/README.md index b057059ff36..61952eda61f 100644 --- a/doc/cost-models/README.md +++ b/doc/cost-models/README.md @@ -59,7 +59,7 @@ doc/cost-models/ ## Data Sources - **Benchmark Data**: `plutus-core/cost-model/data/benching-conway.csv` -- **Cost Models**: `plutus-core/cost-model/data/builtinCostModelC.json` +- **Cost Models**: `plutus-core/cost-model/data/builtinCostModelE.json` Data is loaded dynamically from the Plutus repository using the browser's `fetch()` API. @@ -164,7 +164,7 @@ No build process required - the workflow copies static files directly to `gh-pag **Model not found:** -- Verify function name matches key in `builtinCostModelC.json` +- Verify function name matches key in `builtinCostModelE.json` - Check console for detailed error messages **Plot not rendering:** diff --git a/doc/cost-models/index.html b/doc/cost-models/index.html index a910eb1c6b8..2709b3ee1ad 100644 --- a/doc/cost-models/index.html +++ b/doc/cost-models/index.html @@ -20,6 +20,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -135,6 +136,16 @@

    Available Visualizations

    +
  • + MultiIndexArray +

    + Looks up a list of indices in a Plutus array; linear in the number of indices, + independent of the array size. + (3D visualization: Haystack Size × Needles Size vs Time, plus per-index time + distribution histogram) +

    +
  • +
  • IndexByteString

    @@ -157,7 +168,7 @@

    Data Sources

  • Cost Models: - builtinCostModelC.json + builtinCostModelE.json
  • @@ -239,7 +250,7 @@

    Troubleshooting

  • Model not found: Verify function name matches the key in - builtinCostModelC.json + builtinCostModelE.json
  • Plot not rendering: Check Plotly errors in console; diff --git a/doc/cost-models/indexarray/index.html b/doc/cost-models/indexarray/index.html index e725a71a7fb..abea7a3cb8f 100644 --- a/doc/cost-models/indexarray/index.html +++ b/doc/cost-models/indexarray/index.html @@ -18,6 +18,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -118,7 +119,7 @@

    Plot Information

    Data sources:
    benching-conway.csv
    -
    builtinCostModelC.json
    +
    builtinCostModelE.json
    diff --git a/doc/cost-models/indexarray/plot.js b/doc/cost-models/indexarray/plot.js index d9395de3ffa..1f4b7c78816 100644 --- a/doc/cost-models/indexarray/plot.js +++ b/doc/cost-models/indexarray/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/indexbytestring/index.html b/doc/cost-models/indexbytestring/index.html index 7a231f6c2a7..19f60a6ec37 100644 --- a/doc/cost-models/indexbytestring/index.html +++ b/doc/cost-models/indexbytestring/index.html @@ -21,6 +21,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -121,7 +122,7 @@

    Plot Information

    Data sources:
    benching-conway.csv
    -
    builtinCostModelC.json
    +
    builtinCostModelE.json
    diff --git a/doc/cost-models/indexbytestring/plot.js b/doc/cost-models/indexbytestring/plot.js index 108ba86f7e8..9e7143896e3 100644 --- a/doc/cost-models/indexbytestring/plot.js +++ b/doc/cost-models/indexbytestring/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/insertcoin/index.html b/doc/cost-models/insertcoin/index.html index 369997f21e4..ca10a825dcf 100644 --- a/doc/cost-models/insertcoin/index.html +++ b/doc/cost-models/insertcoin/index.html @@ -21,6 +21,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -111,7 +112,7 @@

    Plot Information

    Data sources:
    benching-conway.csv
    -
    builtinCostModelC.json
    +
    builtinCostModelE.json
    diff --git a/doc/cost-models/insertcoin/plot.js b/doc/cost-models/insertcoin/plot.js index 335c84332f7..c9c9d22484e 100644 --- a/doc/cost-models/insertcoin/plot.js +++ b/doc/cost-models/insertcoin/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/lengthofarray/index.html b/doc/cost-models/lengthofarray/index.html index a9b26a54af2..96b2a109971 100644 --- a/doc/cost-models/lengthofarray/index.html +++ b/doc/cost-models/lengthofarray/index.html @@ -18,6 +18,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -118,7 +119,7 @@

    Plot Information

    Data sources:
    benching-conway.csv
    -
    builtinCostModelC.json
    +
    builtinCostModelE.json
    diff --git a/doc/cost-models/lengthofarray/plot.js b/doc/cost-models/lengthofarray/plot.js index f3318e746ce..1c8aca40cac 100644 --- a/doc/cost-models/lengthofarray/plot.js +++ b/doc/cost-models/lengthofarray/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/listtoarray/index.html b/doc/cost-models/listtoarray/index.html index 79b671c4351..b130db78dab 100644 --- a/doc/cost-models/listtoarray/index.html +++ b/doc/cost-models/listtoarray/index.html @@ -18,6 +18,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -118,7 +119,7 @@

    Plot Information

    Data sources:
    benching-conway.csv
    -
    builtinCostModelC.json
    +
    builtinCostModelE.json
    diff --git a/doc/cost-models/listtoarray/plot.js b/doc/cost-models/listtoarray/plot.js index 0e662273655..ed6040f5aa1 100644 --- a/doc/cost-models/listtoarray/plot.js +++ b/doc/cost-models/listtoarray/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/lookupcoin/index.html b/doc/cost-models/lookupcoin/index.html index 14aec23a63c..df4df0639ce 100644 --- a/doc/cost-models/lookupcoin/index.html +++ b/doc/cost-models/lookupcoin/index.html @@ -18,6 +18,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -118,7 +119,7 @@

    Plot Information

    Data sources:
    benching-conway.csv
    -
    builtinCostModelC.json
    +
    builtinCostModelE.json
    diff --git a/doc/cost-models/lookupcoin/plot.js b/doc/cost-models/lookupcoin/plot.js index 582afd6854d..e41dcd0bafe 100644 --- a/doc/cost-models/lookupcoin/plot.js +++ b/doc/cost-models/lookupcoin/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/multiindexarray/index.html b/doc/cost-models/multiindexarray/index.html new file mode 100644 index 00000000000..9015ff206a0 --- /dev/null +++ b/doc/cost-models/multiindexarray/index.html @@ -0,0 +1,166 @@ + + + + + + MultiIndexArray - Plutus Cost Model Visualization + + + + + + +
    +

    MultiIndexArray Cost Model Visualization

    +

    + Interactive visualization of benchmark data and fitted cost model for the MultiIndexArray builtin function. + This function looks up a list of indices (needles) in a Plutus array (haystack); the cost is linear in the + number of indices and independent of the array size, so the model plane in the 3D view is flat along the + haystack axis. The number of indices is limited, because beyond that limit the execution time is not + predictable from the index count alone; the histogram below shows the per-index time distribution. +

    + +
    +

    Data Source Configuration

    +
    +
    + +
    + + +
    +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + +
    +

    Plot Controls

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + +
    +
    +
    Loading data and generating plot...
    +
    + +
    +

    Plot Information

    + +
    +
    +
    X-axis:
    +
    Haystack size (array length, log scale)
    + +
    Y-axis:
    +
    Needles size (length of the index list)
    + +
    Z-axis:
    +
    Time (nanoseconds)
    + +
    Description:
    +
    Each point is one benchmark. The model plane is flat along the + haystack axis and slopes along the needles axis.
    +
    +
    + +
    +
    Cost Model Type:
    +
    Loading...
    + +
    Model Formula (net):
    +
    Loading...
    + +
    Overhead:
    +
    Loading...
    +
    + +
    +
    +
    Data points:
    +
    -
    + +
    Index count range:
    +
    -
    + +
    Array size range:
    +
    -
    + +
    Time range:
    +
    -
    +
    +
    + +
    +
    Data sources:
    +
    benching-conway.csv
    +
    builtinCostModelE.json + (PlutusV3 from PV 11 / vanRossem)
    +
    +
    +
    + +

    Per-index time distribution

    +

    + Net time per index, (t − overhead) / indexCount, for benchmarks with at least the + selected number of indices. The dashed line marks the net slope of the fitted model. +

    +
    +
    Loading...
    +
    +
    + + + + + + + diff --git a/doc/cost-models/multiindexarray/plot.js b/doc/cost-models/multiindexarray/plot.js new file mode 100644 index 00000000000..6fcbd32518d --- /dev/null +++ b/doc/cost-models/multiindexarray/plot.js @@ -0,0 +1,489 @@ +// MultiIndexArray plot configuration and rendering +// +// Benchmark names are MultiIndexArray//, so +// args[0] = array size (haystack) and args[1] = index count (needles). The +// cost model is linear_in_y: linear in the number of indices, independent of +// the array size. +// +// The main view is a 3D scatter (haystack size, needles size, time) with the +// fitted model drawn as a plane: flat along the haystack axis, sloping along +// the needles axis. A histogram of net per-index times at large index counts +// makes the distribution of the benchmark results (and any multi-modality) +// directly visible. + +// Configuration +const FUNCTION_NAME = 'MultiIndexArray'; // CSV uses PascalCase +const COST_MODEL_NAME = 'multiIndexArray'; // JSON uses camelCase +const ARITY = 2; + +// Default configuration +const DEFAULT_BRANCH = 'master'; +const URL_TEMPLATE = 'https://raw.githubusercontent.com/IntersectMBO/plutus/{BRANCH}/plutus-core/cost-model/data'; + +// LocalStorage keys +const STORAGE_KEYS = { + BRANCH: 'plutus-viz-branch', + CSV_URL: 'plutus-viz-csv-url', + JSON_URL: 'plutus-viz-json-url', + DATA_SOURCE_COLLAPSED: 'plutus-viz-data-source-collapsed', + PLOT_CONTROLS_COLLAPSED: 'plutus-viz-plot-controls-collapsed' +}; + +// Global state +let benchmarkData = []; +let modelPredictions = []; +let costModel = null; +let overhead = 0; +let showModel = true; +let zAxisMode = 'zero'; +let histThreshold = 2000; + +// Generate URL from template and branch +function generateUrlFromBranch(branch) { + return URL_TEMPLATE.replace('{BRANCH}', branch); +} + +// Get file URLs. Variant E is the PlutusV3 cost model from the vanRossem +// hard fork (PV 11) onwards (C is the previous V3 era). +function getFileUrls(baseUrl) { + return { + csv: `${baseUrl}/benching-conway.csv`, + json: `${baseUrl}/builtinCostModelE.json` + }; +} + +// Load settings from localStorage (URL param takes precedence) +function loadSettings() { + const urlBranch = getBranchFromUrl(); + return { + branch: urlBranch || localStorage.getItem(STORAGE_KEYS.BRANCH) || DEFAULT_BRANCH, + csvUrl: localStorage.getItem(STORAGE_KEYS.CSV_URL) || '', + jsonUrl: localStorage.getItem(STORAGE_KEYS.JSON_URL) || '', + collapsed: localStorage.getItem(STORAGE_KEYS.DATA_SOURCE_COLLAPSED) === 'true' + }; +} + +// Save settings to localStorage +function saveSettings(branch, csvUrl, jsonUrl) { + localStorage.setItem(STORAGE_KEYS.BRANCH, branch); + localStorage.setItem(STORAGE_KEYS.CSV_URL, csvUrl); + localStorage.setItem(STORAGE_KEYS.JSON_URL, jsonUrl); +} + +// Update URL fields based on branch name +function updateUrlsFromBranch() { + const branchInput = document.getElementById('branch-name'); + const csvInput = document.getElementById('csv-url'); + const jsonInput = document.getElementById('json-url'); + + const branch = branchInput.value.trim() || DEFAULT_BRANCH; + const baseUrl = generateUrlFromBranch(branch); + const urls = getFileUrls(baseUrl); + + csvInput.value = urls.csv; + jsonInput.value = urls.json; +} + +// Initialize +async function init() { + // Load saved settings + const settings = loadSettings(); + + // Set up collapsible data source section + const dataSourceControls = document.getElementById('data-source-controls'); + const dataSourceToggle = document.getElementById('data-source-toggle'); + + if (settings.collapsed) { + dataSourceControls.classList.add('collapsed'); + } + + dataSourceToggle.addEventListener('click', () => { + const isCollapsed = dataSourceControls.classList.toggle('collapsed'); + localStorage.setItem(STORAGE_KEYS.DATA_SOURCE_COLLAPSED, isCollapsed); + }); + + // Set up collapsible plot controls section + const plotControls = document.getElementById('plot-controls'); + const plotControlsToggle = document.getElementById('plot-controls-toggle'); + + if (localStorage.getItem(STORAGE_KEYS.PLOT_CONTROLS_COLLAPSED) === 'true') { + plotControls.classList.add('collapsed'); + } + + plotControlsToggle.addEventListener('click', () => { + const isCollapsed = plotControls.classList.toggle('collapsed'); + localStorage.setItem(STORAGE_KEYS.PLOT_CONTROLS_COLLAPSED, isCollapsed); + }); + + // Set up form inputs + const branchInput = document.getElementById('branch-name'); + const csvInput = document.getElementById('csv-url'); + const jsonInput = document.getElementById('json-url'); + + // Initialize with saved or default values + branchInput.value = settings.branch; + + if (settings.csvUrl && settings.jsonUrl) { + csvInput.value = settings.csvUrl; + jsonInput.value = settings.jsonUrl; + } else { + updateUrlsFromBranch(); + } + + // Update URLs when branch name changes + branchInput.addEventListener('input', updateUrlsFromBranch); + + // Load data button + const reloadButton = document.getElementById('reload-data'); + reloadButton.addEventListener('click', async () => { + const branch = branchInput.value.trim() || DEFAULT_BRANCH; + const csvUrl = csvInput.value.trim(); + const jsonUrl = jsonInput.value.trim(); + + // Save settings + saveSettings(branch, csvUrl, jsonUrl); + + // Load data + await loadAndRenderData(); + }); + + // Set up plot control event listeners (only once) + setupControls(); + + // Set up copy link button + document.getElementById('copy-link').addEventListener('click', () => { + const branch = document.getElementById('branch-name').value.trim() || DEFAULT_BRANCH; + const url = new URL(window.location.href); + url.search = ''; + url.searchParams.set('branch', branch); + navigator.clipboard.writeText(url.toString()); + // Brief visual feedback + const btn = document.getElementById('copy-link'); + const original = btn.textContent; + btn.textContent = 'Copied!'; + setTimeout(() => btn.textContent = original, 1500); + }); + + // Initial load + await loadAndRenderData(); +} + +// Load and render data +async function loadAndRenderData() { + // Show loading state + const container = document.getElementById('plot-container'); + container.innerHTML = '
    Loading data and generating plot...
    '; + + try { + // Get URLs from form inputs + const csvUrl = document.getElementById('csv-url').value.trim(); + const jsonUrl = document.getElementById('json-url').value.trim(); + + if (!csvUrl || !jsonUrl) { + showError('Please provide both CSV and JSON file URLs'); + return; + } + + // Load data + const { parsedData, costModelJson, overheadMap } = await loadData(csvUrl, jsonUrl); + + // Filter for this function + benchmarkData = filterByFunction(parsedData, FUNCTION_NAME); + + if (benchmarkData.length === 0) { + showError(`No benchmark data found for ${FUNCTION_NAME}`); + return; + } + + // Extract cost model + costModel = extractCostModel(costModelJson, COST_MODEL_NAME); + + // Get overhead + overhead = overheadMap[ARITY] || 0; + + // Generate model predictions + if (costModel) { + modelPredictions = generateModelPredictions(benchmarkData, costModel, overhead); + } + + // Update info panel + updateInfoPanel(); + + // Render plots + renderPlot(); + renderHistogram(); + + } catch (error) { + console.error('Initialization error:', error); + showError(`Failed to load data. Check console for details. Error: ${error.message}`); + } +} + +function updateInfoPanel() { + // Calculate stats over the index count (second argument) + const stats = calculateStats(benchmarkData, 1); + + // Update data points + document.getElementById('info-data-points').textContent = stats.dataPoints; + + // Update ranges + if (stats.minArg !== undefined) { + document.getElementById('info-x-range').textContent = `${stats.minArg} - ${stats.maxArg}`; + } + + const arraySizes = benchmarkData.map(d => d.args[0]); + document.getElementById('info-array-range').textContent = + `${Math.min(...arraySizes)} - ${Math.max(...arraySizes)}`; + + document.getElementById('info-time-range').textContent = stats.timeRange; + + // Update model info + if (costModel) { + document.getElementById('info-model-type').textContent = costModel.modelType; + document.getElementById('info-model-formula').textContent = formatModelFormula( + costModel.modelType, + costModel.coefficients + ); + } else { + document.getElementById('info-model-type').textContent = 'Not available'; + document.getElementById('info-model-formula').textContent = 'Cost model not found'; + } + + // Update overhead + if (overhead > 0) { + document.getElementById('info-overhead').textContent = + `${overhead.toFixed(2)} ns (arity ${ARITY}) added to predictions`; + } else { + document.getElementById('info-overhead').textContent = 'Not calculated'; + } +} + +// The model plane: z = intercept + slope * needles (+ overhead), flat along +// the haystack axis. +function modelPlaneTrace() { + const haystacks = [...new Set(benchmarkData.map(d => d.args[0]))].sort((a, b) => a - b); + const maxNeedles = Math.max(...benchmarkData.map(d => d.args[1])); + const steps = 30; + const needles = Array.from({ length: steps + 1 }, (_, i) => Math.max(1, Math.round(i * maxNeedles / steps))); + + const intercept = costModel.coefficients.intercept || 0; + const slope = costModel.coefficients.slope || 0; + + // z[i][j] corresponds to (y = needles[i], x = haystacks[j]) + const z = needles.map(n => haystacks.map(() => (intercept + slope * n) / 1000 + overhead)); + + return { + x: haystacks, + y: needles, + z: z, + type: 'surface', + name: 'Model Prediction', + showscale: false, + opacity: 0.35, + colorscale: [[0, '#E53E3E'], [1, '#E53E3E']], + hovertemplate: 'needles: %{y}
    model: %{z:.0f} ns' + }; +} + +function renderPlot() { + const benchmarkTrace = { + x: benchmarkData.map(d => d.args[0]), + y: benchmarkData.map(d => d.args[1]), + z: benchmarkData.map(d => d.time), + mode: 'markers', + type: 'scatter3d', + name: 'Benchmark Data', + hovertemplate: 'haystack: %{x}
    needles: %{y}
    time: %{z:.0f} ns', + marker: { + size: 3.5, + color: '#0033AD', + opacity: 0.75 + } + }; + + const traces = [benchmarkTrace]; + + if (showModel && costModel && costModel.modelType === 'linear_in_y') { + traces.push(modelPlaneTrace()); + } else if (showModel && modelPredictions.length > 0) { + // Fallback for other model shapes: prediction markers at the data points + traces.push({ + x: modelPredictions.map(d => d.args[0]), + y: modelPredictions.map(d => d.args[1]), + z: modelPredictions.map(d => d.predictedTime), + mode: 'markers', + type: 'scatter3d', + name: 'Model Predictions', + marker: { size: 3.5, color: '#E53E3E', opacity: 0.4, symbol: 'x' } + }); + } + + const benchmarkZ = benchmarkTrace.z; + + // Layout configuration + const layout = { + title: { + text: `${FUNCTION_NAME} - Benchmark vs Model (3D)`, + font: { size: 20 } + }, + scene: { + xaxis: { + title: 'Haystack Size (array, log)', + type: 'log', + gridcolor: '#E0E0E0' + }, + yaxis: { + title: 'Needles Size (index count)', + gridcolor: '#E0E0E0' + }, + zaxis: { + title: 'Time (nanoseconds)', + gridcolor: '#E0E0E0' + }, + camera: { + eye: { x: 1.7, y: -1.7, z: 0.6 } + } + }, + showlegend: true, + legend: { + x: 0.02, + y: 0.98, + bgcolor: 'rgba(255, 255, 255, 0.8)', + bordercolor: '#BDC3C7', + borderwidth: 1 + }, + paper_bgcolor: 'rgba(0,0,0,0)' + }; + + // Set Z-axis range based on mode + if (zAxisMode === 'zero') { + layout.scene.zaxis.range = [0, Math.max(...benchmarkZ) * 1.1]; + } else { + const minZ = Math.min(...benchmarkZ); + const maxZ = Math.max(...benchmarkZ); + const padding = (maxZ - minZ) * 0.1; + layout.scene.zaxis.range = [minZ - padding, maxZ + padding]; + } + + // Config + const config = { + responsive: true, + displayModeBar: true, + displaylogo: false + }; + + // Render + const container = document.getElementById('plot-container'); + container.innerHTML = ''; + Plotly.newPlot('plot-container', traces, layout, config); +} + +function renderHistogram() { + const container = document.getElementById('hist-container'); + + // Net per-index time for large index counts + const points = benchmarkData.filter(d => d.args[1] >= histThreshold); + if (points.length === 0) { + container.innerHTML = `

    No benchmarks with index count ≥ ${histThreshold}

    `; + return; + } + + const perIndex = points.map(d => (d.time - overhead) / d.args[1]); + + const histTrace = { + x: perIndex, + type: 'histogram', + name: `per-index time (y ≥ ${histThreshold})`, + xbins: { size: 1 }, + marker: { + color: '#0033AD', + opacity: 0.8 + } + }; + + const layout = { + title: { + text: `Distribution of net per-index time (index count ≥ ${histThreshold}, n = ${points.length})`, + font: { size: 18 } + }, + xaxis: { + title: 'Net time per index (nanoseconds)', + gridcolor: '#E0E0E0' + }, + yaxis: { + title: 'Benchmark count', + gridcolor: '#E0E0E0' + }, + bargap: 0.05, + plot_bgcolor: '#FAFAFA', + paper_bgcolor: 'rgba(0,0,0,0)', + shapes: [], + annotations: [] + }; + + // Mark the model's net slope (ps -> ns) on the distribution + if (costModel && (costModel.modelType === 'linear_in_y')) { + const slopeNs = (costModel.coefficients.slope || 0) / 1000; + layout.shapes.push({ + type: 'line', + x0: slopeNs, x1: slopeNs, + y0: 0, y1: 1, + yref: 'paper', + line: { color: '#E53E3E', width: 2, dash: 'dash' } + }); + layout.annotations.push({ + x: slopeNs, + y: 1, + yref: 'paper', + yanchor: 'bottom', + text: `model slope: ${slopeNs.toFixed(1)} ns/index`, + showarrow: false, + font: { color: '#E53E3E' } + }); + } + + const config = { + responsive: true, + displayModeBar: true, + displaylogo: false + }; + + container.innerHTML = ''; + Plotly.newPlot('hist-container', [histTrace], layout, config); +} + +function setupControls() { + // Show/hide model checkbox + const showModelCheckbox = document.getElementById('show-model'); + showModelCheckbox.addEventListener('change', (e) => { + showModel = e.target.checked; + renderPlot(); + }); + + // Z-axis mode selector + const zAxisModeSelect = document.getElementById('z-axis-mode'); + zAxisModeSelect.addEventListener('change', (e) => { + zAxisMode = e.target.value; + renderPlot(); + }); + + // Histogram threshold selector + const histThresholdSelect = document.getElementById('hist-threshold'); + histThresholdSelect.addEventListener('change', (e) => { + histThreshold = parseInt(e.target.value, 10); + renderHistogram(); + }); +} + +function showError(message) { + const container = document.getElementById('plot-container'); + container.innerHTML = ` +
    +

    Error Loading Data

    +

    ${message}

    +
    + `; +} + +// Start initialization when page loads +document.addEventListener('DOMContentLoaded', init); diff --git a/doc/cost-models/scalevalue/index.html b/doc/cost-models/scalevalue/index.html index a90ea6a96a4..de472c0c792 100644 --- a/doc/cost-models/scalevalue/index.html +++ b/doc/cost-models/scalevalue/index.html @@ -21,6 +21,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -111,7 +112,7 @@

    Plot Information

    diff --git a/doc/cost-models/scalevalue/plot.js b/doc/cost-models/scalevalue/plot.js index e75c32dd9bb..e143b0326e7 100644 --- a/doc/cost-models/scalevalue/plot.js +++ b/doc/cost-models/scalevalue/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/shared/utils.js b/doc/cost-models/shared/utils.js index 6d3b62f7694..786d4b06f6d 100644 --- a/doc/cost-models/shared/utils.js +++ b/doc/cost-models/shared/utils.js @@ -239,7 +239,7 @@ function evaluateCostModel(modelType, coefficients, args) { } /** - * Extract cost model from builtinCostModelC.json for a specific function + * Extract cost model from builtinCostModelE.json for a specific function * Returns { modelType, coefficients } or null if not found */ function extractCostModel(costModelJson, functionName) { diff --git a/doc/cost-models/unionvalue/index.html b/doc/cost-models/unionvalue/index.html index 6355c4b206f..b54196f287a 100644 --- a/doc/cost-models/unionvalue/index.html +++ b/doc/cost-models/unionvalue/index.html @@ -21,6 +21,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -113,7 +114,7 @@

    Plot Information

    diff --git a/doc/cost-models/unionvalue/plot.js b/doc/cost-models/unionvalue/plot.js index f67dadce4b3..17904ffc9ac 100644 --- a/doc/cost-models/unionvalue/plot.js +++ b/doc/cost-models/unionvalue/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/unvaluedata/index.html b/doc/cost-models/unvaluedata/index.html index 3c576666959..633be6f3744 100644 --- a/doc/cost-models/unvaluedata/index.html +++ b/doc/cost-models/unvaluedata/index.html @@ -18,6 +18,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -118,7 +119,7 @@

    Plot Information

    diff --git a/doc/cost-models/unvaluedata/plot.js b/doc/cost-models/unvaluedata/plot.js index 9a6d9fe7e00..d7d450522d0 100644 --- a/doc/cost-models/unvaluedata/plot.js +++ b/doc/cost-models/unvaluedata/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/valuecontains/index.html b/doc/cost-models/valuecontains/index.html index cb08638a1ae..1032af7847c 100644 --- a/doc/cost-models/valuecontains/index.html +++ b/doc/cost-models/valuecontains/index.html @@ -18,6 +18,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -124,7 +125,7 @@

    Plot Information

    diff --git a/doc/cost-models/valuecontains/plot.js b/doc/cost-models/valuecontains/plot.js index a5128285e72..ccf9e145efd 100644 --- a/doc/cost-models/valuecontains/plot.js +++ b/doc/cost-models/valuecontains/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/doc/cost-models/valuedata/index.html b/doc/cost-models/valuedata/index.html index 4f991ba1059..0a0e0f989b1 100644 --- a/doc/cost-models/valuedata/index.html +++ b/doc/cost-models/valuedata/index.html @@ -18,6 +18,7 @@
  • ListToArray
  • LengthOfArray
  • IndexArray
  • +
  • MultiIndexArray
  • IndexByteString
  • @@ -118,7 +119,7 @@

    Plot Information

    diff --git a/doc/cost-models/valuedata/plot.js b/doc/cost-models/valuedata/plot.js index aed9be39d04..e7d227afbe4 100644 --- a/doc/cost-models/valuedata/plot.js +++ b/doc/cost-models/valuedata/plot.js @@ -35,7 +35,7 @@ function generateUrlFromBranch(branch) { function getFileUrls(baseUrl) { return { csv: `${baseUrl}/benching-conway.csv`, - json: `${baseUrl}/builtinCostModelC.json` + json: `${baseUrl}/builtinCostModelE.json` }; } diff --git a/plutus-core/changelog.d/20260727_115456_yuriy.lazaryev_issue_2259_multi_index_array_costing.md b/plutus-core/changelog.d/20260727_115456_yuriy.lazaryev_issue_2259_multi_index_array_costing.md new file mode 100644 index 00000000000..9956e20d5f7 --- /dev/null +++ b/plutus-core/changelog.d/20260727_115456_yuriy.lazaryev_issue_2259_multi_index_array_costing.md @@ -0,0 +1,3 @@ +### Changed + +- Replaced the placeholder costing of `multiIndexArray` ([CIP-0156](https://cips.cardano.org/cip/CIP-0156)) with a benchmarked cost model: CPU linear in the number of indices, memory linear in the length of the result. `multiIndexArray` now fails if given more than 4096 indices, because beyond that its execution time is not predictable from the index count alone. diff --git a/plutus-core/cost-model/budgeting-bench/Benchmarks/Arrays.hs b/plutus-core/cost-model/budgeting-bench/Benchmarks/Arrays.hs index f75c12932ed..ad0cd8cc878 100644 --- a/plutus-core/cost-model/budgeting-bench/Benchmarks/Arrays.hs +++ b/plutus-core/cost-model/budgeting-bench/Benchmarks/Arrays.hs @@ -1,5 +1,6 @@ {-# LANGUAGE BlockArguments #-} {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} module Benchmarks.Arrays (makeBenchmarks) where @@ -7,26 +8,44 @@ module Benchmarks.Arrays (makeBenchmarks) where import Prelude import Common -import Control.Monad (replicateM) +import Control.Monad (replicateM, replicateM_) import Criterion.Main (Benchmark) +import Data.Bits (bit) import Data.ByteString (ByteString) +import Data.Set qualified as Set +import Data.Traversable (for) import Data.Vector.Strict (Vector) import Data.Vector.Strict qualified as Vector +import GHC.Compact (compact, compactAdd, getCompact) +import PlutusCore.Arrays qualified as Arrays import PlutusCore.Builtin (mkTyBuiltin) import PlutusCore.Core (Type) -import PlutusCore.Default (DefaultFun (IndexArray, LengthOfArray, ListToArray), DefaultUni) +import PlutusCore.Default + ( DefaultFun (IndexArray, LengthOfArray, ListToArray, MultiIndexArray) + , DefaultUni + ) import PlutusCore.Name.Unique (TyName) -import System.Random.Stateful (StdGen, UniformRange (uniformRM), runStateGen_, uniformByteStringM) +import System.Random (mkStdGen, randomRs) +import System.Random.Stateful + ( StatefulGen + , StdGen + , UniformRange (uniformRM) + , runStateGen_ + , uniformByteStringM + ) -------------------------------------------------------------------------------- -- Benchmarks ------------------------------------------------------------------ -makeBenchmarks :: StdGen -> [Benchmark] -makeBenchmarks gen = - [ benchLengthOfArray gen - , benchListToArray gen - , benchIndexArray gen - ] +makeBenchmarks :: StdGen -> IO [Benchmark] +makeBenchmarks gen = do + multiIndexArray <- benchMultiIndexArray gen + pure + [ benchLengthOfArray gen + , benchListToArray gen + , benchIndexArray gen + , multiIndexArray + ] benchLengthOfArray :: StdGen -> Benchmark benchLengthOfArray gen = @@ -66,6 +85,141 @@ benchIndexArray gen = idx <- uniformRM (0, arraySize - 1) g pure (vec, fromIntegral idx) +{- The cost of `multiIndexArray` is expected to be linear in the number of indices + (y) and independent of the array size (x) and of the element size, since each + lookup is a constant-time read. The inputs are chosen so that the benchmark + data can also demonstrate that independence rather than assume it: + + * a full grid of array sizes crossed with index counts, so that x and y vary + independently; array sizes are log-spaced up to 131072 to cover all cache + regimes (131072 elements is already at the practical limit of what + `listToArray` can build within one transaction budget); + * indices drawn uniformly over the whole array, with replacement -- random + access is the cache-adversarial pattern; + * off-grid random points supporting residual diagnostics; + * two cells with ~1 KiB elements (64 bytes elsewhere) as an empirical check + that element size does not affect the cost; their index counts are offset + from the grid values so that benchmark names stay unique. + + See Note [Scattered index lists] for why the index lists are allocated the + way they are. -} +benchMultiIndexArray :: StdGen -> IO Benchmark +benchMultiIndexArray gen = do + pairs <- for inputs \(vec, indices, widths) -> (vec,) <$> mkScatteredList widths indices + pure (createTwoTermBuiltinBenchElementwise MultiIndexArray [tyArrayOfBS] pairs) + where + arraySizes :: [Int] + arraySizes = [1, 8, 64, 512, 4_096, 32_768, 131_072] + + -- The top of the range is 'Arrays.maximumIndexCount', so that the model is + -- fitted over exactly the domain the denotation accepts. + indexCounts :: [Int] + indexCounts = [1, 10, 50, 100, 500, 1_000, 2_000, 3_000, Arrays.maximumIndexCount] + + inputs :: [(Vector ByteString, [Integer], [Int])] + inputs = dedupeOnSizes $ runStateGen_ gen \g -> do + grid <- + concat <$> for arraySizes \arraySize -> do + vec <- mkArray g 64 arraySize + for indexCounts (fmap (withGaps vec) . mkIndices g arraySize) + cloud <- replicateM 37 do + arraySize <- (2 ^) <$> uniformRM (0 :: Int, 17) g + indexCount <- uniformRM (1, Arrays.maximumIndexCount) g + vec <- mkArray g 64 arraySize + withGaps vec <$> mkIndices g arraySize indexCount + control <- for [999, 1_001] \indexCount -> do + vec <- mkArray g 1_024 4_096 + withGaps vec <$> mkIndices g 4_096 indexCount + -- A distinct array size, so these get benchmark names of their own. + spread <- for spreadCounts \indexCount -> do + vec <- mkArray g 64 2_048 + withBudgetGaps vec <$> mkIndices g 2_048 indexCount + pure (grid <> control <> cloud <> spread) + + mkArray :: StatefulGen g m => g -> Int -> Int -> m (Vector ByteString) + mkArray g maxElemSize arraySize = + Vector.replicateM arraySize do + bsSize <- uniformRM (0, maxElemSize) g + uniformByteStringM bsSize g + + mkIndices :: StatefulGen g m => g -> Int -> Int -> m [Integer] + mkIndices g arraySize indexCount = + replicateM indexCount (fromIntegral <$> uniformRM (0, arraySize - 1) g) + + -- Index counts that additionally get the wider spacing described in Note + -- [Scattered index lists]; the top of the range is among them because the + -- fit takes the slowest observation at each end. + spreadCounts :: [Int] + spreadCounts = [500, 2_000, Arrays.maximumIndexCount] + + withGaps vec indices = (vec, indices, gapWidths) + + withBudgetGaps vec indices = + (vec, indices, repeat (max 1 (budgetScatterBytes `div` (length indices * spacerBytes)))) + + -- Number of spacer objects per gap. Varied rather than fixed: a constant + -- stride is picked up by the hardware prefetcher, which makes a regularly + -- spaced list measurably faster than an irregularly spaced one. The mean + -- is 16, so 4 KiB per gap. + gapWidths :: [Int] + gapWidths = randomRs (2, 30) (mkStdGen 7) + +-- | Bytes of one spacer object, an integer of 2048 bits plus its header. +spacerBytes :: Int +spacerBytes = 256 + +{-| Total scatter for the widely spaced inputs, of the order a transaction's +memory budget allows. -} +budgetScatterBytes :: Int +budgetScatterBytes = 100_000_000 + +{- Note [Scattered index lists] +The time to walk a list depends on where its cons cells sit in memory, not only +on how many there are, and a list built by a plain `replicateM` is allocated +contiguously, which is the favourable end of that range rather than a +representative point. These lists are therefore built with the cells some way +apart, and by varying amounts, so that the model is fitted to the unfavourable +end. Most inputs get gaps of about a page; a few, including the largest index +count, get gaps of the width that a whole transaction's memory budget would +allow, because that is what sets the slope. See also Note +[Index count limitation for multiIndexArray] in PlutusCore.Arrays. + +Three implementation details are needed for that layout to hold, and removing +any of them silently returns the benchmark to measuring contiguous lists: + + * the list is built inside a compact region, because the ordinary heap is + re-laid by every major GC and the layout would not survive; + * `compactAdd` appends in call order, so the cells and the spacers between + them are interleaved as written; + * each gap is filled with several small objects rather than one large one, + because a large object is given a block of its own and the cells would end + up adjacent. -} +mkScatteredList :: [Int] -> [Integer] -> IO [Integer] +mkScatteredList gapWidths values = do + region <- compact () + let go _ acc [] = pure acc + go (width : widths) acc (v : vs) = do + replicateM_ width (compactAdd region spacer) + acc' <- getCompact <$> compactAdd region (v : acc) + go widths acc' vs + go [] acc _ = pure acc + go gapWidths [] (reverse values) + where + spacer = bit 2_048 :: Integer + +-- Benchmark names are derived from the argument sizes; drop the rare random +-- point that collides with another point on both sizes. +dedupeOnSizes + :: [(Vector ByteString, [Integer], [Int])] -> [(Vector ByteString, [Integer], [Int])] +dedupeOnSizes = go Set.empty + where + go _ [] = [] + go seen (input@(vec, indices, _) : rest) + | Set.member key seen = go seen rest + | otherwise = input : go (Set.insert key seen) rest + where + key = (Vector.length vec, length indices) + -------------------------------------------------------------------------------- -- Helpers --------------------------------------------------------------------- diff --git a/plutus-core/cost-model/budgeting-bench/Main.hs b/plutus-core/cost-model/budgeting-bench/Main.hs index af2e3aa4648..5de73a92a98 100644 --- a/plutus-core/cost-model/budgeting-bench/Main.hs +++ b/plutus-core/cost-model/budgeting-bench/Main.hs @@ -45,6 +45,8 @@ main = do -- We use the initial state of gen repeatedly below, but that doesn't matter. gen <- System.Random.getStdGen + arrayBenchmarks <- Benchmarks.Arrays.makeBenchmarks gen + criterionMainWith Start defaultConfig @@ -55,7 +57,7 @@ main = do <> Benchmarks.Data.makeBenchmarks gen <> Benchmarks.Integers.makeBenchmarks gen <> Benchmarks.Lists.makeBenchmarks gen - <> Benchmarks.Arrays.makeBenchmarks gen + <> arrayBenchmarks <> Benchmarks.Misc.makeBenchmarks gen <> Benchmarks.Pairs.makeBenchmarks gen <> Benchmarks.Strings.makeBenchmarks gen diff --git a/plutus-core/cost-model/create-cost-model/BuiltinMemoryModels.hs b/plutus-core/cost-model/create-cost-model/BuiltinMemoryModels.hs index 9ebaa999ad6..4bd1db3e5fa 100644 --- a/plutus-core/cost-model/create-cost-model/BuiltinMemoryModels.hs +++ b/plutus-core/cost-model/create-cost-model/BuiltinMemoryModels.hs @@ -378,6 +378,10 @@ builtinMemoryModels = paramUnionValue = Id $ ModelTwoArgumentsAddedSizes $ OneVariableLinearFunction 24 21 , -- See Note [Memory model for Value builtins] paramScaleValue = Id $ ModelTwoArgumentsLinearInY $ OneVariableLinearFunction 12 21 + , -- The result is a list of length y (the index list) whose elements are shared with + -- the array; only the spine is new. The nonzero intercept keeps the charge positive + -- for the empty index list. + paramMultiIndexArray = Id $ ModelTwoArgumentsLinearInY $ OneVariableLinearFunction 4 1 } where identityFunction = OneVariableLinearFunction 0 1 diff --git a/plutus-core/cost-model/create-cost-model/CreateBuiltinCostModel.hs b/plutus-core/cost-model/create-cost-model/CreateBuiltinCostModel.hs index 041b262affc..3cdb4c13cd9 100644 --- a/plutus-core/cost-model/create-cost-model/CreateBuiltinCostModel.hs +++ b/plutus-core/cost-model/create-cost-model/CreateBuiltinCostModel.hs @@ -153,6 +153,7 @@ builtinCostModelNames = , paramInsertCoin = "insertCoinModel" , paramUnionValue = "unionValueModel" , paramScaleValue = "scaleValueModel" + , paramMultiIndexArray = "multiIndexArrayModel" } {-| Loads the models from R. @@ -308,6 +309,7 @@ createBuiltinCostModel bmfile rfile = do paramInsertCoin <- getParams readCF4 paramInsertCoin paramUnionValue <- getParams readCF2 paramUnionValue paramScaleValue <- getParams readCF2 paramScaleValue + paramMultiIndexArray <- getParams readCF2 paramMultiIndexArray pure $ BuiltinCostModelBase {..} diff --git a/plutus-core/cost-model/data/benching-conway.csv b/plutus-core/cost-model/data/benching-conway.csv index b05d0901126..899f5219a1a 100644 --- a/plutus-core/cost-model/data/benching-conway.csv +++ b/plutus-core/cost-model/data/benching-conway.csv @@ -14812,3 +14812,108 @@ EqualsString/19400/19400,2.3089679419295826e-6,2.307911008840468e-6,2.3099943277 EqualsString/19600/19600,2.3625558068055665e-6,2.3601398190871273e-6,2.3650285798307642e-6,8.292779808420567e-9,6.6631051322127245e-9,1.034399864901697e-8 EqualsString/19800/19800,2.4843644080488102e-6,2.4806302153117747e-6,2.488255193448198e-6,1.3014923018139571e-8,1.1712144644861913e-8,1.4644209347218475e-8 EqualsString/20000/20000,2.3788835774653483e-6,2.3780147413486273e-6,2.379744446079645e-6,2.87726733474497e-9,2.3580396483665765e-9,3.492220006626607e-9 +MultiIndexArray/1/1,1.1475654799036273e-6,1.1448647666255405e-6,1.1494661411069994e-6,7.336311295137874e-9,4.9305201847747694e-9,9.41030756760755e-9 +MultiIndexArray/1/10,1.2287963259220548e-6,1.2266598094460012e-6,1.231049946721095e-6,7.119507549868057e-9,6.194678698444273e-9,8.309631247881801e-9 +MultiIndexArray/1/50,1.772882633224413e-6,1.7717321639707322e-6,1.7746752681524706e-6,4.819206016465468e-9,3.0936671367289426e-9,7.0585005894483825e-9 +MultiIndexArray/1/100,2.3141830697380424e-6,2.3125471098691155e-6,2.3166777020821787e-6,6.4580013686935525e-9,4.507409501755394e-9,8.435816731108233e-9 +MultiIndexArray/1/500,7.54895584546493e-6,7.545735731302061e-6,7.555654903574705e-6,1.4711393929776863e-8,7.679703010274258e-9,2.4531083461401574e-8 +MultiIndexArray/1/1000,1.4373719306632369e-5,1.4364656255743034e-5,1.4388399912049631e-5,3.895335429148531e-8,2.5016816849137794e-8,5.8767616985778457e-8 +MultiIndexArray/1/2000,4.097176576218451e-5,4.093763475215707e-5,4.103207890194546e-5,1.4550724883715165e-7,8.981074944513769e-8,2.3391057549129412e-7 +MultiIndexArray/1/3000,8.113751816082237e-5,8.109148491887528e-5,8.11802992087285e-5,1.419540456921008e-7,1.1196868656376282e-7,1.9049490583748957e-7 +MultiIndexArray/1/4096,1.3701346477622284e-4,1.3696701577405333e-4,1.3705763689726027e-4,1.5975502393310089e-7,1.2925721451299196e-7,1.9169143622945698e-7 +MultiIndexArray/8/1,1.1392937345937184e-6,1.1383095530905438e-6,1.1405786428788913e-6,3.526421610808158e-9,2.6648294049810025e-9,5.382127012659372e-9 +MultiIndexArray/8/10,1.2208659818681312e-6,1.2197316788984498e-6,1.2223620214918156e-6,4.374249285666737e-9,3.354642880734952e-9,6.1613569160514995e-9 +MultiIndexArray/8/50,1.7634832002011692e-6,1.7613970037177715e-6,1.7656414271621546e-6,7.168260900226481e-9,5.65386264696535e-9,9.459612525490319e-9 +MultiIndexArray/8/100,2.3059404656518384e-6,2.304650806415747e-6,2.3080089338174866e-6,5.370163722971263e-9,3.957616573618578e-9,9.243882059024596e-9 +MultiIndexArray/8/500,7.518873919207095e-6,7.516823517398488e-6,7.521351331224648e-6,7.725571239991918e-9,6.760193563105144e-9,9.277378736114766e-9 +MultiIndexArray/8/1000,1.4306821413415305e-5,1.4296208582175293e-5,1.4327342978395375e-5,5.1786217652817766e-8,3.246431273223711e-8,7.888591904843396e-8 +MultiIndexArray/8/2000,4.2936908237012715e-5,4.270519891202728e-5,4.3200162013554305e-5,8.352598678852213e-7,6.143744278367033e-7,9.727270830258463e-7 +MultiIndexArray/8/3000,8.426967713660737e-5,8.422303391656001e-5,8.433355547110183e-5,1.8242417045987494e-7,1.3617894415774104e-7,2.7388733989182697e-7 +MultiIndexArray/8/4096,1.4111856886058413e-4,1.4103955923905998e-4,1.4126359118156573e-4,3.406395406303189e-7,2.3992327516243965e-7,5.601111047619069e-7 +MultiIndexArray/64/1,1.1320884766997998e-6,1.1311306213818535e-6,1.132949552122866e-6,3.1395012705821005e-9,2.627034954638711e-9,3.9358854778687525e-9 +MultiIndexArray/64/10,1.2259711396834285e-6,1.2249421123437414e-6,1.2270777354140446e-6,3.5962585334345135e-9,3.026159818235465e-9,4.486410259979114e-9 +MultiIndexArray/64/50,1.7552211270884605e-6,1.753943006688894e-6,1.7565707149845192e-6,4.537811424987793e-9,3.886308255754433e-9,5.471169115751113e-9 +MultiIndexArray/64/100,2.3162329611548037e-6,2.314662175764795e-6,2.3175684496433603e-6,5.040900066020265e-9,4.05014171882415e-9,6.627757915662408e-9 +MultiIndexArray/64/500,7.4815816439649375e-6,7.478412113345307e-6,7.485547490797605e-6,1.20468021151101e-8,9.330349377660013e-9,1.5668631922134665e-8 +MultiIndexArray/64/1000,1.4312130640555895e-5,1.430208968053619e-5,1.4337211667185642e-5,4.9116315090714124e-8,2.5987470147307084e-8,9.505203802834015e-8 +MultiIndexArray/64/2000,4.227015667914413e-5,4.2252467689218185e-5,4.230858305188174e-5,8.42548760861762e-8,2.885618989074955e-8,1.488143277085926e-7 +MultiIndexArray/64/3000,8.343204882953575e-5,8.34017702613872e-5,8.346506271638361e-5,1.0643542974439423e-7,8.664862295930653e-8,1.329079506729003e-7 +MultiIndexArray/64/4096,1.41055418824186e-4,1.4099417972956754e-4,1.4118648327130154e-4,2.765722806791926e-7,1.5691144191559164e-7,4.94464052983363e-7 +MultiIndexArray/512/1,1.1491569963874003e-6,1.147867844970278e-6,1.1504893036573095e-6,4.594687568580564e-9,3.7919507601732794e-9,5.624201227327765e-9 +MultiIndexArray/512/10,1.2139260915999734e-6,1.2120291398556885e-6,1.2163837282742807e-6,7.044246829066238e-9,5.895597023193336e-9,8.48491652380167e-9 +MultiIndexArray/512/50,1.7688493632225068e-6,1.7682129004866559e-6,1.7696391280397623e-6,2.343350885189239e-9,2.0010632088768125e-9,2.744018153434179e-9 +MultiIndexArray/512/100,2.3324068087519933e-6,2.330364957669374e-6,2.3371545574096404e-6,1.0116817788475386e-8,4.3964989190151555e-9,1.902652709184929e-8 +MultiIndexArray/512/500,7.513035940046098e-6,7.511407728964184e-6,7.5151364015400585e-6,6.491319946331427e-9,4.960052804183259e-9,9.31247014325348e-9 +MultiIndexArray/512/1000,1.431227731614314e-5,1.4299559278823689e-5,1.433825492343175e-5,5.8725584636161884e-8,3.2442840872572496e-8,8.953670279358393e-8 +MultiIndexArray/512/2000,4.332768369800322e-5,4.3302717787585554e-5,4.3373650100383065e-5,1.0786290442572388e-7,7.034971384426688e-8,1.8200996406745133e-7 +MultiIndexArray/512/3000,8.456077936202956e-5,8.44777801937079e-5,8.478131150443767e-5,3.865107574762306e-7,1.8536058183793767e-7,7.443922468937351e-7 +MultiIndexArray/512/4096,1.412353937008207e-4,1.4117694700985779e-4,1.412820257318807e-4,1.6689281103757434e-7,1.209659860146293e-7,2.3660800804491056e-7 +MultiIndexArray/4096/1,1.1330780818426288e-6,1.1309826286519326e-6,1.1356419388689413e-6,7.802377479510687e-9,6.015136236752066e-9,1.2392032765013144e-8 +MultiIndexArray/4096/10,1.2234123089556797e-6,1.2222981653012294e-6,1.224542341892705e-6,3.8999605249312146e-9,3.322911526406136e-9,4.906415499922072e-9 +MultiIndexArray/4096/50,1.779932834256638e-6,1.7782447767933912e-6,1.782569516265636e-6,7.553144885592049e-9,4.821681507187308e-9,1.3230294457254247e-8 +MultiIndexArray/4096/100,2.3629184328353204e-6,2.3615837148380844e-6,2.3643934649915683e-6,4.70758529262968e-9,3.995590414070982e-9,5.995015672034042e-9 +MultiIndexArray/4096/500,7.65780242347359e-6,7.64566798555546e-6,7.67058184807567e-6,4.195757724752069e-8,3.7619109878055244e-8,4.871542086748444e-8 +MultiIndexArray/4096/1000,1.4655624969748715e-5,1.4650198658955465e-5,1.466385011974229e-5,2.1975236791326868e-8,1.4916605554880984e-8,3.235149632830576e-8 +MultiIndexArray/4096/2000,4.445840125088027e-5,4.442715379876741e-5,4.454054374865683e-5,1.613248594860919e-7,6.351805534742639e-8,3.4048044327364157e-7 +MultiIndexArray/4096/3000,8.620297907392301e-5,8.616462463083014e-5,8.624949327577044e-5,1.5473820576351706e-7,1.1785814619392178e-7,1.9668064598653916e-7 +MultiIndexArray/4096/4096,1.4286459388620134e-4,1.4277916252570695e-4,1.4317990369608233e-4,4.912531341569871e-7,1.0168418957511721e-7,1.024313150273432e-6 +MultiIndexArray/32768/1,1.139095064820247e-6,1.135867678547784e-6,1.1422286873184639e-6,1.110786565170614e-8,1.0146347183482831e-8,1.2354436664396808e-8 +MultiIndexArray/32768/10,1.215784710912282e-6,1.2145190832833523e-6,1.2172680154626224e-6,4.79895033900198e-9,3.838096557513749e-9,5.931043554246286e-9 +MultiIndexArray/32768/50,1.7646720115865439e-6,1.7631190070124947e-6,1.7673232213897585e-6,6.499390288308522e-9,4.1498692909168484e-9,9.68979108207791e-9 +MultiIndexArray/32768/100,2.345901853766103e-6,2.3415271042014816e-6,2.35979439383955e-6,2.4269945973437666e-8,3.194780840687806e-9,4.9783974016169465e-8 +MultiIndexArray/32768/500,7.69663070352817e-6,7.691841977394466e-6,7.706118855197925e-6,2.128805678151139e-8,1.1803952928196037e-8,3.7417286068938697e-8 +MultiIndexArray/32768/1000,1.5283789391021987e-5,1.5276037662443826e-5,1.5290765682236745e-5,2.524027469112948e-8,1.9995544574569156e-8,3.50107417567014e-8 +MultiIndexArray/32768/2000,4.7466156869556694e-5,4.7456921388254045e-5,4.747615691802702e-5,3.196410826986061e-8,2.354343551161185e-8,4.6684433059645994e-8 +MultiIndexArray/32768/3000,9.273293857269934e-5,9.265639419281239e-5,9.28612221515432e-5,3.474524722195476e-7,2.38497384022849e-7,4.887424602472412e-7 +MultiIndexArray/32768/4096,1.478418033420807e-4,1.477954621365976e-4,1.4788930670819238e-4,1.501350448254087e-7,1.1735054770023855e-7,1.9706071422865002e-7 +MultiIndexArray/131072/1,1.1405141567995614e-6,1.1398900949463115e-6,1.141199796480311e-6,2.185308208014028e-9,1.7995683009088735e-9,2.746686083056155e-9 +MultiIndexArray/131072/10,1.2264376729483102e-6,1.22514086755324e-6,1.2280743055865163e-6,4.847321839671195e-9,4.0680931963426486e-9,6.0676714817473e-9 +MultiIndexArray/131072/50,1.7727715025108328e-6,1.772200307914582e-6,1.7734399543422059e-6,2.0229779837689575e-9,1.5753146306727805e-9,2.618455704434526e-9 +MultiIndexArray/131072/100,2.3358516566583567e-6,2.3347474625533538e-6,2.338580258163134e-6,5.434431634241336e-9,2.5246094663854225e-9,1.101900724745063e-8 +MultiIndexArray/131072/500,7.80633624337251e-6,7.801651206860497e-6,7.812067657571503e-6,1.747587908781229e-8,1.4461629406548155e-8,2.20084019392088e-8 +MultiIndexArray/131072/1000,1.5184784186853412e-5,1.5180518738317496e-5,1.519166061529206e-5,1.783767003442113e-8,1.2142956774713305e-8,2.8893209846427368e-8 +MultiIndexArray/131072/2000,5.137118273073598e-5,5.134764699193158e-5,5.1408435167352416e-5,9.629255282785384e-8,4.9591811440584155e-8,1.7831287364070714e-7 +MultiIndexArray/131072/3000,9.604944727217142e-5,9.60067437455425e-5,9.612357962602096e-5,1.866359335791601e-7,1.1744395698604175e-7,3.508752995715949e-7 +MultiIndexArray/131072/4096,1.502647411983423e-4,1.502136281389219e-4,1.5033325092279765e-4,1.998748526852996e-7,1.4668450618535053e-7,2.6945330304703006e-7 +MultiIndexArray/4096/999,1.4721282684320545e-5,1.4713773533638093e-5,1.4737354542682606e-5,3.5308767633145565e-8,2.0761797816617353e-8,5.673555242207037e-8 +MultiIndexArray/4096/1001,1.459397290521482e-5,1.4573057035927441e-5,1.4629822284386054e-5,8.727279670791087e-8,5.563132795996642e-8,1.2865033798757422e-7 +MultiIndexArray/8/263,4.5250542916656e-6,4.521644276564486e-6,4.530814209738934e-6,1.3945745674475767e-8,8.287353506306776e-9,2.1789086132499053e-8 +MultiIndexArray/16/1524,2.353516106753335e-5,2.352380196133849e-5,2.3563125069345923e-5,5.53277646914903e-8,2.994654972933111e-8,1.0073468802146534e-7 +MultiIndexArray/8/1667,2.627155449868186e-5,2.6257099871177818e-5,2.6290911310549556e-5,5.7208734487057544e-8,3.926089471389008e-8,8.772835684991053e-8 +MultiIndexArray/64/3655,1.1461294093104726e-4,1.1456987918497553e-4,1.1466675903960892e-4,1.5902299177238653e-7,1.224317112766101e-7,2.107107450081871e-7 +MultiIndexArray/32768/3460,1.1346848110292192e-4,1.1341364308116417e-4,1.1358228874055657e-4,2.610285437970092e-7,1.6343007130663884e-7,4.3669176593170376e-7 +MultiIndexArray/256/3239,9.574960288134122e-5,9.5690994621022e-5,9.585958811861784e-5,2.541167867974683e-7,1.7536489048612754e-7,3.7636526239578784e-7 +MultiIndexArray/1/3047,8.252546903090217e-5,8.248224689617068e-5,8.256259573497199e-5,1.329117456911557e-7,1.0006050871687976e-7,1.867489838466605e-7 +MultiIndexArray/8192/2991,8.772723087989315e-5,8.7698323656832e-5,8.775778855318519e-5,1.0332989679305222e-7,7.704805872946415e-8,1.621167010107353e-7 +MultiIndexArray/256/3030,8.492788104086702e-5,8.489646304064705e-5,8.497109943434013e-5,1.2462255823133606e-7,7.858386222648809e-8,2.37266722065449e-7 +MultiIndexArray/16384/3123,9.512370129639336e-5,9.509280245097904e-5,9.515541133674835e-5,1.0827419975506337e-7,9.138505202122216e-8,1.2732090003283284e-7 +MultiIndexArray/4/338,5.47988444175722e-6,5.476923058413485e-6,5.483135774029877e-6,1.068765667380798e-8,7.99774127776263e-9,1.546116088567235e-8 +MultiIndexArray/2/1031,1.4795635890269815e-5,1.4784339843427506e-5,1.4812942385747425e-5,4.9718340706618514e-8,3.487878734529445e-8,7.265277934928343e-8 +MultiIndexArray/4096/4011,1.390622272872614e-4,1.389895143739499e-4,1.3912858192554803e-4,2.4567351393944767e-7,1.938071076867939e-7,3.72087173508944e-7 +MultiIndexArray/8192/4068,1.424863690676321e-4,1.4243422145372408e-4,1.4254091049147865e-4,1.777086836136699e-7,1.438985005082449e-7,2.343514031453711e-7 +MultiIndexArray/16384/985,1.4753610167168232e-5,1.4745602190529964e-5,1.4775973032705035e-5,3.823825665445562e-8,1.2969891692770693e-8,6.720058605117397e-8 +MultiIndexArray/256/1397,2.103095123681931e-5,2.1009082958736477e-5,2.1079080615089097e-5,1.037630178707584e-7,5.6927647374429493e-8,1.9091732462565374e-7 +MultiIndexArray/16/2479,5.985903431555217e-5,5.9829127469029275e-5,5.991642104227068e-5,1.3169560793205766e-7,7.24143994049018e-8,2.639134483206058e-7 +MultiIndexArray/1024/2109,4.636769412175354e-5,4.6336774922766416e-5,4.6426284963908745e-5,1.5168816901747882e-7,9.08900920243067e-8,2.3572553495188033e-7 +MultiIndexArray/65536/233,4.116917961204163e-6,4.113619201489099e-6,4.120101395973758e-6,1.1294020796427668e-8,9.064908666906267e-9,1.4549133101343258e-8 +MultiIndexArray/2048/2546,6.384519492491547e-5,6.382843204654578e-5,6.387694452194656e-5,7.570252063435817e-8,4.957308560057083e-8,1.2656411262420802e-7 +MultiIndexArray/16384/2990,9.03461892176389e-5,9.030935317535446e-5,9.043236463360032e-5,1.7633014211490778e-7,9.23281765024184e-8,3.2716591246767657e-7 +MultiIndexArray/1024/2411,5.831821851659926e-5,5.828135543585365e-5,5.8370462924800955e-5,1.4255734214841028e-7,1.1361708092774552e-7,2.002840843115711e-7 +MultiIndexArray/16384/1208,1.8480162179741e-5,1.8464649959009443e-5,1.850558255246869e-5,6.551899866049371e-8,4.2758893815082787e-8,9.478748818004945e-8 +MultiIndexArray/1024/2062,4.4619444604977835e-5,4.4600924501096825e-5,4.465141505214709e-5,8.377867517991318e-8,5.434323931720216e-8,1.3127280634022865e-7 +MultiIndexArray/2/3608,1.1188293982609802e-4,1.1183945162956827e-4,1.1197090314295894e-4,1.9667735759930496e-7,1.2083727624015923e-7,3.262129500083901e-7 +MultiIndexArray/16/97,2.4823970942278015e-6,2.481537663958302e-6,2.4833037653944116e-6,3.081132886173127e-9,2.621900700890028e-9,3.772393754364903e-9 +MultiIndexArray/32768/3231,9.398453910740249e-5,9.393726830449062e-5,9.40690416818357e-5,1.9889222753302612e-7,1.1846484642570863e-7,3.1930278049249855e-7 +MultiIndexArray/128/2754,5.8043185840921895e-5,5.800201363155572e-5,5.81379189985672e-5,2.04225242168635e-7,1.1116545718764201e-7,3.520235989113438e-7 +MultiIndexArray/65536/766,1.1481053172428057e-5,1.1470379473150677e-5,1.1501112461943436e-5,5.019474883606618e-8,3.246657519999301e-8,7.621161149791926e-8 +MultiIndexArray/16/450,6.93077870315652e-6,6.92275708718419e-6,6.946923581774483e-6,3.7917792623943404e-8,2.1114437978407046e-8,7.259068340845737e-8 +MultiIndexArray/65536/3084,8.550630092711685e-5,8.54726620843229e-5,8.555405070544366e-5,1.260570707376488e-7,9.054816949416432e-8,2.095250762250309e-7 +MultiIndexArray/2/2396,5.131983437353318e-5,5.1292703738830464e-5,5.1344814139262904e-5,8.847353010179114e-8,6.904037339874972e-8,1.211942358841752e-7 +MultiIndexArray/32/2432,5.3625158113025835e-5,5.358443585000692e-5,5.3692570003589664e-5,1.795031276719089e-7,1.3259950936975455e-7,2.91639524715725e-7 +MultiIndexArray/65536/1945,4.0132919756194e-5,4.011879828921036e-5,4.0146385977486055e-5,4.6121676649527215e-8,3.8768711146407936e-8,5.607901063350481e-8 +MultiIndexArray/2/1289,1.9068479076250185e-5,1.9051591075347515e-5,1.9093807261806103e-5,7.011693718403845e-8,5.279000203213367e-8,8.874610155790319e-8 +MultiIndexArray/512/1263,1.8849133144478524e-5,1.8840978129336474e-5,1.8861763991409942e-5,3.3357142338041636e-8,1.524494327100234e-8,6.031231197953646e-8 +MultiIndexArray/8192/1265,1.9601851029324906e-5,1.9589704033696178e-5,1.9623598996945176e-5,5.4416153053893326e-8,3.177356746550693e-8,9.995780598723585e-8 +MultiIndexArray/2048/500,7.975582554909001e-6,7.970026548609899e-6,7.994467836430784e-6,3.050891915403152e-8,6.182880825230999e-9,6.683492044704189e-8 +MultiIndexArray/2048/2000,6.058574489365401e-5,6.053941682980067e-5,6.064511336602131e-5,1.7535636675356376e-7,1.2146341099574307e-7,2.744845750175178e-7 +MultiIndexArray/2048/4096,1.7478629954996368e-4,1.7473945019987552e-4,1.748418933028428e-4,1.8076306047619194e-7,1.4062703370780312e-7,2.3776232441823307e-7 diff --git a/plutus-core/cost-model/data/builtinCostModelA.json b/plutus-core/cost-model/data/builtinCostModelA.json index 6448e7ec150..22511b1c9d0 100644 --- a/plutus-core/cost-model/data/builtinCostModelA.json +++ b/plutus-core/cost-model/data/builtinCostModelA.json @@ -1321,5 +1321,21 @@ }, "type": "linear_in_y" } + }, + "multiIndexArray": { + "cpu": { + "arguments": { + "intercept": 268678, + "slope": 42403 + }, + "type": "linear_in_y" + }, + "memory": { + "arguments": { + "intercept": 4, + "slope": 1 + }, + "type": "linear_in_y" + } } } diff --git a/plutus-core/cost-model/data/builtinCostModelB.json b/plutus-core/cost-model/data/builtinCostModelB.json index 80ae94bf2e8..702d1b703da 100644 --- a/plutus-core/cost-model/data/builtinCostModelB.json +++ b/plutus-core/cost-model/data/builtinCostModelB.json @@ -1321,5 +1321,21 @@ }, "type": "linear_in_y" } + }, + "multiIndexArray": { + "cpu": { + "arguments": { + "intercept": 268678, + "slope": 42403 + }, + "type": "linear_in_y" + }, + "memory": { + "arguments": { + "intercept": 4, + "slope": 1 + }, + "type": "linear_in_y" + } } } diff --git a/plutus-core/cost-model/data/builtinCostModelC.json b/plutus-core/cost-model/data/builtinCostModelC.json index 5ae7455e37c..9b59b55357e 100644 --- a/plutus-core/cost-model/data/builtinCostModelC.json +++ b/plutus-core/cost-model/data/builtinCostModelC.json @@ -1339,5 +1339,21 @@ }, "type": "linear_in_y" } + }, + "multiIndexArray": { + "cpu": { + "arguments": { + "intercept": 268678, + "slope": 42403 + }, + "type": "linear_in_y" + }, + "memory": { + "arguments": { + "intercept": 4, + "slope": 1 + }, + "type": "linear_in_y" + } } } diff --git a/plutus-core/cost-model/data/builtinCostModelD.json b/plutus-core/cost-model/data/builtinCostModelD.json index 9abde57904b..23de8e49bd2 100644 --- a/plutus-core/cost-model/data/builtinCostModelD.json +++ b/plutus-core/cost-model/data/builtinCostModelD.json @@ -1321,5 +1321,21 @@ }, "type": "linear_in_y" } + }, + "multiIndexArray": { + "cpu": { + "arguments": { + "intercept": 268678, + "slope": 42403 + }, + "type": "linear_in_y" + }, + "memory": { + "arguments": { + "intercept": 4, + "slope": 1 + }, + "type": "linear_in_y" + } } } diff --git a/plutus-core/cost-model/data/builtinCostModelE.json b/plutus-core/cost-model/data/builtinCostModelE.json index 66c73b35ca0..f6ab436f2bf 100644 --- a/plutus-core/cost-model/data/builtinCostModelE.json +++ b/plutus-core/cost-model/data/builtinCostModelE.json @@ -1339,5 +1339,21 @@ }, "type": "linear_in_y" } + }, + "multiIndexArray": { + "cpu": { + "arguments": { + "intercept": 268678, + "slope": 42403 + }, + "type": "linear_in_y" + }, + "memory": { + "arguments": { + "intercept": 4, + "slope": 1 + }, + "type": "linear_in_y" + } } } diff --git a/plutus-core/cost-model/data/models.R b/plutus-core/cost-model/data/models.R index 81cbddfefa9..178e6275daf 100644 --- a/plutus-core/cost-model/data/models.R +++ b/plutus-core/cost-model/data/models.R @@ -159,6 +159,7 @@ arity <- function(name) { "ValueData" = 1, "UnValueData" = 1, "ScaleValue" = 2, + "MultiIndexArray" = 2, -1 ## Default for missing values ) } @@ -815,6 +816,40 @@ modelFun <- function(path) { lengthOfArrayModel <- constantModel ("LengthOfArray") listToArrayModel <- linearInX ("ListToArray") indexArrayModel <- constantModel ("IndexArray") + ## Cost is linear in the number of indices (y) and independent of the array + ## size (x). Per-index time rises slowly across the benchmarked range, which + ## makes the data slightly convex, and least squares on convex data returns a + ## negative intercept: the fit moves the fixed cost of a call into the slope, + ## adjustModel then clamps the intercept, and the fixed cost is lost. Fitting + ## the line through the extremes of the range keeps it, and for convex data + ## that line is an upper bound in between. The fit is checked against every + ## point below. + multiIndexArrayModel <- { + fname <- "MultiIndexArray" + filtered <- data %>% + filter.and.check.nonempty (fname) %>% + discard.overhead () + lo <- min(filtered$y_mem) + hi <- max(filtered$y_mem) + if (lo == hi) { + stop ("Need at least two distinct index counts for ", fname) + } + ## Slowest observation at each end, so that the line also covers the + ## spread of repeated measurements. + ends <- data.frame( + y_mem = c(lo, hi), + t = c(max(filter(filtered, y_mem == lo)$t), + max(filter(filtered, y_mem == hi)$t)) + ) + m <- lm(t ~ y_mem, ends) + predicted <- predict(m, filtered) + if (any(filtered$t > predicted)) { + cat (sprintf ("*** WARNING: %d of %d %s points lie above the fitted line (worst %.1f%%)\n", + sum(filtered$t > predicted), nrow(filtered), fname, + 100 * max((filtered$t - predicted) / predicted))) + } + mk.result(m, "linear_in_y") + } ## Values @@ -971,7 +1006,8 @@ modelFun <- function(path) { unValueDataModel = unValueDataModel, insertCoinModel = insertCoinModel, unionValueModel = unionValueModel, - scaleValueModel = scaleValueModel + scaleValueModel = scaleValueModel, + multiIndexArrayModel = multiIndexArrayModel ) ## The integer division functions have a complex costing behaviour that requires some negative diff --git a/plutus-core/cost-model/test/TestCostModels.hs b/plutus-core/cost-model/test/TestCostModels.hs index c8f5040956f..917c0e2d0fc 100644 --- a/plutus-core/cost-model/test/TestCostModels.hs +++ b/plutus-core/cost-model/test/TestCostModels.hs @@ -556,4 +556,6 @@ main = , -- FIXME: the test for unValueData fails. This may be because of a rounding error in the top coefficient. -- , $(genTest 1 "unValueData") $(genTest 2 "scaleValue") Everywhere + , -- Arrays (batch 7) + $(genTest 2 "multiIndexArray") Everywhere ] diff --git a/plutus-core/plutus-core.cabal b/plutus-core/plutus-core.cabal index 542f6a47ab8..9d3a2a31927 100644 --- a/plutus-core/plutus-core.cabal +++ b/plutus-core/plutus-core.cabal @@ -104,6 +104,7 @@ library PlutusCore.Analysis.Usages PlutusCore.Annotation PlutusCore.Arity + PlutusCore.Arrays PlutusCore.AstSize PlutusCore.Bitwise PlutusCore.Builtin @@ -847,6 +848,7 @@ executable cost-model-budgeting-bench , deepseq , directory , filepath + , ghc-compact , hedgehog , mtl , optparse-applicative diff --git a/plutus-core/plutus-core/src/PlutusCore/Arrays.hs b/plutus-core/plutus-core/src/PlutusCore/Arrays.hs new file mode 100644 index 00000000000..08991e15e7e --- /dev/null +++ b/plutus-core/plutus-core/src/PlutusCore/Arrays.hs @@ -0,0 +1,68 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | Implementation for the array builtins of CIP-0156 that needs more than a one-liner. +module PlutusCore.Arrays + ( multiIndexArray + , maximumIndexCount + ) where + +import PlutusCore.Builtin.Result (BuiltinResult, builtinResultFailure, emit) + +import Data.Text (pack) +import Data.Vector.Strict (Vector) +import Data.Vector.Strict qualified as Vector + +{- Note [Index count limitation for multiIndexArray] +Looking up a list of indices means walking that list, and the time to walk a +list is not determined by its length alone, so execution time stops being +predictable from the index count beyond some size. We therefore limit the +number of indices, and the cost model is fitted over exactly that range. The +limit is far above any realistic use: a transaction is limited to 16384 bytes, +so a serialised list of this many indices would not fit even if the transaction +carried nothing else. This limit may be raised once costing can bound the cost +without it, but note that doing so would need a second variant of the builtin so +that existing scripts keep their current behaviour. + +Compare Note [Input length limitation for IntegerToByteString], which limits +those builtins for the same underlying reason. + +The count is checked during the traversal rather than up front, because taking +the length first would walk the list twice. -} +maximumIndexCount :: Int +maximumIndexCount = 4096 +{-# INLINE maximumIndexCount #-} + +{-| Look up every index of the given list in the given array. + +Fails if any index is out of bounds, or if there are more than +'maximumIndexCount' of them; see Note [Index count limitation for +multiIndexArray]. + +The elements are read eagerly, as in @indexArray@, so that the read is paid for +inside this builtin rather than wherever the element is later demanded. Forcing +is safe: the vector is strict, so the element is already in normal form as far +as this function is concerned. -} +multiIndexArray :: forall a. Vector a -> [Integer] -> BuiltinResult [a] +multiIndexArray vec = go 0 + where + !len = toInteger (Vector.length vec) + + go :: Int -> [Integer] -> BuiltinResult [a] + go !_ [] = pure [] + go !n (i : is) + | n >= maximumIndexCount = do + emit . pack $ + "multiIndexArray: too many indices (maximum is " + ++ show maximumIndexCount + ++ ")" + builtinResultFailure + | 0 <= i && i < len = do + let !x = Vector.unsafeIndex vec (fromInteger i) + (x :) <$> go (n + 1) is + | otherwise = do + emit "multiIndexArray: array index out of bounds" + emit $ "Index: " <> (pack . show $ i) + builtinResultFailure +{-# INLINE multiIndexArray #-} diff --git a/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs b/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs index 1a3d2c7bb00..d38465960cf 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs @@ -18,6 +18,7 @@ module PlutusCore.Default.Builtins where import PlutusPrelude +import PlutusCore.Arrays qualified as Arrays import PlutusCore.Builtin import PlutusCore.Data (Data (..)) import PlutusCore.Default.Universe @@ -2484,17 +2485,13 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where multiIndexArrayDenotation (SomeConstant (Some (ValueOf uni vec))) indices = case uni of DefaultUniArray uniA -> - let len = toInteger (Vector.length vec) - lookupIndex i - | 0 <= i && i < len = pure $ Vector.unsafeIndex vec $ fromInteger i - | otherwise = fail "Array index out of bounds" - in fromValueOf (DefaultUniList uniA) <$> traverse lookupIndex indices + fromValueOf (DefaultUniList uniA) <$> Arrays.multiIndexArray vec indices _ -> throwError $ structuralUnliftingError "Expected an array but got something else" {-# INLINE multiIndexArrayDenotation #-} in makeBuiltinMeaning multiIndexArrayDenotation - (runCostingFunTwoArguments . unimplementedCostingFun) + (runCostingFunTwoArguments . paramMultiIndexArray) toBuiltinMeaning _semvar Policies = let policiesDenotation :: Value -> [ByteString] policiesDenotation = Value.policies diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/BuiltinCostModel.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/BuiltinCostModel.hs index 1784d99e627..abeda8fb489 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/BuiltinCostModel.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/BuiltinCostModel.hs @@ -199,6 +199,8 @@ data BuiltinCostModelBase f , paramInsertCoin :: f ModelFourArguments , paramUnionValue :: f ModelTwoArguments , paramScaleValue :: f ModelTwoArguments + , -- Batch 7 + paramMultiIndexArray :: f ModelTwoArguments } deriving stock (Generic) deriving anyclass (FunctorB, TraversableB, ConstraintsB) diff --git a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/ExBudgetingDefaults.hs b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/ExBudgetingDefaults.hs index 39b4755f1db..dc08e6c3c8e 100644 --- a/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/ExBudgetingDefaults.hs +++ b/plutus-core/plutus-core/src/PlutusCore/Evaluation/Machine/ExBudgetingDefaults.hs @@ -427,6 +427,8 @@ unitCostBuiltinCostModel = , paramInsertCoin = unitCostFourArguments , paramUnionValue = unitCostTwoArguments , paramScaleValue = unitCostTwoArguments + , -- Batch 7 + paramMultiIndexArray = unitCostTwoArguments } unitCekParameters diff --git a/plutus-ledger-api/src/PlutusLedgerApi/V1/ParamName.hs b/plutus-ledger-api/src/PlutusLedgerApi/V1/ParamName.hs index 2ed299b5a94..b7de963f3f4 100644 --- a/plutus-ledger-api/src/PlutusLedgerApi/V1/ParamName.hs +++ b/plutus-ledger-api/src/PlutusLedgerApi/V1/ParamName.hs @@ -349,5 +349,10 @@ data ParamName | ScaleValue'cpu'arguments'slope | ScaleValue'memory'arguments'intercept | ScaleValue'memory'arguments'slope + | -- Batch 7, not yet deployed + MultiIndexArray'cpu'arguments'intercept + | MultiIndexArray'cpu'arguments'slope + | MultiIndexArray'memory'arguments'intercept + | MultiIndexArray'memory'arguments'slope deriving stock (Eq, Ord, Enum, Ix, Bounded, Generic) deriving (IsParamName) via (GenericParamName ParamName) diff --git a/plutus-ledger-api/src/PlutusLedgerApi/V2/ParamName.hs b/plutus-ledger-api/src/PlutusLedgerApi/V2/ParamName.hs index f1c43dda9af..8679d5387ff 100644 --- a/plutus-ledger-api/src/PlutusLedgerApi/V2/ParamName.hs +++ b/plutus-ledger-api/src/PlutusLedgerApi/V2/ParamName.hs @@ -351,5 +351,10 @@ data ParamName | ScaleValue'cpu'arguments'slope | ScaleValue'memory'arguments'intercept | ScaleValue'memory'arguments'slope + | -- Batch 7, not yet deployed + MultiIndexArray'cpu'arguments'intercept + | MultiIndexArray'cpu'arguments'slope + | MultiIndexArray'memory'arguments'intercept + | MultiIndexArray'memory'arguments'slope deriving stock (Eq, Ord, Enum, Ix, Bounded, Generic) deriving (IsParamName) via (GenericParamName ParamName) diff --git a/plutus-ledger-api/src/PlutusLedgerApi/V3/ParamName.hs b/plutus-ledger-api/src/PlutusLedgerApi/V3/ParamName.hs index a8c1dbc60dc..feb73334f9b 100644 --- a/plutus-ledger-api/src/PlutusLedgerApi/V3/ParamName.hs +++ b/plutus-ledger-api/src/PlutusLedgerApi/V3/ParamName.hs @@ -367,5 +367,10 @@ data ParamName | ScaleValue'cpu'arguments'slope | ScaleValue'memory'arguments'intercept | ScaleValue'memory'arguments'slope + | -- Batch 7, not yet deployed + MultiIndexArray'cpu'arguments'intercept + | MultiIndexArray'cpu'arguments'slope + | MultiIndexArray'memory'arguments'intercept + | MultiIndexArray'memory'arguments'slope deriving stock (Eq, Ord, Enum, Ix, Bounded, Generic) deriving (IsParamName) via (GenericParamName ParamName) diff --git a/plutus-ledger-api/test/Spec/CostModelParams.hs b/plutus-ledger-api/test/Spec/CostModelParams.hs index 311cd0753da..7e424676b8c 100644 --- a/plutus-ledger-api/test/Spec/CostModelParams.hs +++ b/plutus-ledger-api/test/Spec/CostModelParams.hs @@ -39,9 +39,9 @@ tests = "CostModelParams" "costModelParams" [ embed $ testCase "length" do - 332 @=? length v1_ParamNames - 332 @=? length v2_ParamNames - 350 @=? length v3_ParamNames + 336 @=? length v1_ParamNames + 336 @=? length v2_ParamNames + 354 @=? length v3_ParamNames , embed $ testCase "tripping paramname" do for_ v1_ParamNames \p -> assertBool "tripping v1 cm params failed" $ diff --git a/plutus-ledger-api/test/Spec/Data/CostModelParams.hs b/plutus-ledger-api/test/Spec/Data/CostModelParams.hs index 6976821abe8..4ed00474149 100644 --- a/plutus-ledger-api/test/Spec/Data/CostModelParams.hs +++ b/plutus-ledger-api/test/Spec/Data/CostModelParams.hs @@ -39,9 +39,9 @@ tests = "CostModelParams" "costModelParams" [ embed $ testCase "length" do - 332 @=? length v1_ParamNames - 332 @=? length v2_ParamNames - 350 @=? length v3_ParamNames + 336 @=? length v1_ParamNames + 336 @=? length v2_ParamNames + 354 @=? length v3_ParamNames , embed $ testCase "tripping paramname" do for_ v1_ParamNames \p -> assertBool "tripping v1 cm params failed" $