Skip to content

perf: optimize LogUp for constant single-column tables (range checks)#1717

Merged
yelhousni merged 5 commits intomasterfrom
perf/rangecheck
Feb 19, 2026
Merged

perf: optimize LogUp for constant single-column tables (range checks)#1717
yelhousni merged 5 commits intomasterfrom
perf/rangecheck

Conversation

@yelhousni
Copy link
Copy Markdown
Contributor

@yelhousni yelhousni commented Feb 13, 2026

Description

Optimizes PLONK (SCS) constraint count for range checks by merging Sub + DivUnchecked into a single AddPlonkConstraint gate on the table side of the log-derivative argument.

In the LogUp verification equation sum count(f,S)/(x-f) == sum 1/(x-s), the left-hand (table) side previously required 3 PLONK gates per table entry:

  1. Sub: denom = challenge - table_val (1 gate)
  2. DivUnchecked: quotient * denom = exps[i] (1 gate)
  3. Add: lp += quotient (1 gate)

For constant single-column tables (which range checks always are), the constant table value can be folded into the PLONK gate coefficients. A batch hint computes quotients, then a single AddPlonkConstraint verifies quotient * (challenge - c) = exps[i] by encoding it as qM*q*ch + qL*q + qO*exps + qC = 0 with qM=1, qL=-c, qO=-1, qC=0. This reduces the table side to 2 gates per entry:

  1. AddPlonkConstraint: verify quotient (1 gate)
  2. Add: lp += quotient (1 gate)

Saving: 2^baseLength - 1 PLONK constraints per circuit (the i=0 entry where Sub(challenge, 0) was already free).

R1CS is unaffected. The base-length selection heuristic is intentionally left unchanged since it implicitly accounts for commitment overhead not modeled in the cost formula.

Type of change

  • New feature (non-breaking change which adds functionality)

How has this been tested?

All existing tests pass across all backends (Groth16/R1CS, PLONK/SCS) and curves (BN254, BLS12-381), including small-field (KoalaBear):

  • go test ./std/rangecheck/ -count=1 — TestCheck, TestCheckSmallField, TestBaseLengthOption
  • go test ./std/lookup/logderivlookup/ -count=1 — TestLookup, Example (uses logderivarg internally)

How has this been benchmarked?

Range check micro-benchmark (100 values, 64-bit, BN254)

old new Δ %
SCS constraints 4,923 4,668 -255 -5.2%

EVM precompiles

Circuit baseLength old new Δ %
ECPair (n=2) 16 1,522,195 1,456,660 -65,535 -4.3%
ECRecover 13 346,687 338,496 -8,191 -2.4%
P256Verify 13 666,146 657,955 -8,191 -1.2%
ECMul 11 210,369 208,322 -2,047 -1.0%
ECAdd 8 5,477 5,222 -255 -4.7%

PLONK recursion

Circuit old new Δ %
BW6-761 in BN254 15,041,991 14,976,446 -65,545 -0.44%

Savings are exactly 2^baseLength - 1 per circuit, confirming the optimization applies correctly.

Checklist:

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • I did not modify files generated from templates
  • golangci-lint does not output errors locally
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Note

Medium Risk
Touches core constraint-generation logic and introduces a new hint/optimization path specific to PLONK, so incorrect gating or edge cases (non-int constants, zero denominators) could cause subtle proof failures despite the guarded fallback.

Overview
Improves PLONK performance of std/internal/logderivarg when the lookup table is a constant single-column table (typical for range checks) by verifying quotient*(challenge-c)=exps[i] via one AddPlonkConstraint per entry instead of building Sub + DivUnchecked constraints.

Adds and registers a new solver hint batchDivBySubHint to compute the quotients for this optimized path, while keeping the existing fallback behavior for non-constant/multi-column tables and for small-field (wide commitment) mode unchanged. Updates internal/stats/latest_stats.csv to reflect reduced PLONK constraint counts in affected benchmarks.

Written by Cursor Bugbot for commit 4ea74d9. This will update automatically on new commits. Configure here.

@yelhousni yelhousni requested a review from ivokub February 13, 2026 18:47
@yelhousni yelhousni self-assigned this Feb 13, 2026
@yelhousni yelhousni added dep: linea Issues affecting Linea downstream type: perf labels Feb 13, 2026
@yelhousni yelhousni added this to the v0.14.N milestone Feb 13, 2026
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Comment thread std/internal/logderivarg/logderivarg.go
Copy link
Copy Markdown
Collaborator

@ivokub ivokub left a comment

Choose a reason for hiding this comment

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

Beautiful! I didn't think there was room for optimizing logderivarg package any further :) (actually there is I think for small-field case we don't need to use linear combination but can pack the values into the extension accumulators, but this can do in another PR)

@yelhousni yelhousni merged commit d89d4a7 into master Feb 19, 2026
15 of 16 checks passed
@yelhousni yelhousni deleted the perf/rangecheck branch February 19, 2026 01:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dep: linea Issues affecting Linea downstream type: perf

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants