Skip to content

perf: Zip MDS rows in the inner apply_mds loop - #60

Open
vadorovsky wants to merge 1 commit into
Lightprotocol:mainfrom
vadorovsky:vad/apply-mds
Open

vadorovsky wants to merge 1 commit into
Lightprotocol:mainfrom
vadorovsky:vad/apply-mds

Conversation

@vadorovsky

@vadorovsky vadorovsky commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Indexing the MDS matrix as mds[i][j] bounds-checks and chases the row pointer on every inner iteration. Zipping the state iterator with the row slice removes both and improves performance.

Before:

poseidon_bn254_x5_1     time:   [23.550 µs 23.586 µs 23.629 µs]
poseidon_bn254_x5_2     time:   [33.843 µs 33.914 µs 33.990 µs]
poseidon_bn254_x5_3     time:   [45.972 µs 46.023 µs 46.080 µs]
poseidon_bn254_x5_4     time:   [64.794 µs 64.868 µs 64.950 µs]
poseidon_bn254_x5_5     time:   [83.945 µs 84.056 µs 84.183 µs]
poseidon_bn254_x5_6     time:   [111.81 µs 111.93 µs 112.08 µs]
poseidon_bn254_x5_7     time:   [143.60 µs 143.76 µs 143.94 µs]
poseidon_bn254_x5_8     time:   [174.56 µs 175.58 µs 176.93 µs]
poseidon_bn254_x5_9     time:   [204.46 µs 204.78 µs 205.15 µs]
poseidon_bn254_x5_10    time:   [271.73 µs 272.08 µs 272.48 µs]
poseidon_bn254_x5_11    time:   [296.99 µs 297.33 µs 297.74 µs]
poseidon_bn254_x5_12    time:   [376.47 µs 376.79 µs 377.26 µs]

After:

poseidon_bn254_x5_1     time:   [23.066 µs 23.174 µs 23.325 µs]
                        change: [-2.2081% -1.8589% -1.3974%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 10 outliers among 100 measurements (10.00%)
  6 (6.00%) high mild
  4 (4.00%) high severe

poseidon_bn254_x5_2     time:   [32.838 µs 32.869 µs 32.904 µs]
                        change: [-3.0838% -2.9449% -2.8031%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
  3 (3.00%) high mild
  4 (4.00%) high severe

poseidon_bn254_x5_3     time:   [44.638 µs 44.666 µs 44.696 µs]
                        change: [-3.1142% -2.9264% -2.7627%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  3 (3.00%) high mild

poseidon_bn254_x5_4     time:   [63.564 µs 63.667 µs 63.789 µs]
                        change: [-1.7487% -1.5015% -1.1982%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) high mild
  4 (4.00%) high severe

poseidon_bn254_x5_5     time:   [82.885 µs 82.967 µs 83.072 µs]
                        change: [-1.5774% -1.3842% -1.2051%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  2 (2.00%) high mild
  4 (4.00%) high severe

poseidon_bn254_x5_6     time:   [110.84 µs 110.96 µs 111.10 µs]
                        change: [-1.0675% -0.9438% -0.8197%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) high mild
  2 (2.00%) high severe

poseidon_bn254_x5_7     time:   [142.01 µs 142.19 µs 142.39 µs]
                        change: [-1.2473% -1.0711% -0.9154%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 5 outliers among 100 measurements (5.00%)
  4 (4.00%) high mild
  1 (1.00%) high severe

poseidon_bn254_x5_8     time:   [173.71 µs 173.91 µs 174.11 µs]
                        change: [-0.9042% -0.5522% -0.2606%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high mild

poseidon_bn254_x5_9     time:   [204.29 µs 205.15 µs 206.12 µs]
                        change: [-0.0241% +0.5845% +1.2478%] (p = 0.06 > 0.05)
                        No change in performance detected.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high mild

poseidon_bn254_x5_10    time:   [267.99 µs 268.63 µs 269.34 µs]
                        change: [-1.3121% -0.9879% -0.7280%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) high mild

poseidon_bn254_x5_11    time:   [292.10 µs 292.62 µs 293.23 µs]
                        change: [-1.6042% -1.4175% -1.2132%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 17 outliers among 100 measurements (17.00%)
  15 (15.00%) high mild
  2 (2.00%) high severe

poseidon_bn254_x5_12    time:   [369.39 µs 370.32 µs 371.35 µs]
                        change: [-2.6819% -2.0366% -1.6086%] (p = 0.00 < 0.05)
                        Performance has improved.

@vadorovsky
vadorovsky force-pushed the vad/apply-mds branch 2 times, most recently from c07b5b7 to 2408b39 Compare September 21, 2026 06:58
@vadorovsky
vadorovsky marked this pull request as ready for review September 21, 2026 06:58
@ananas-block

ananas-block commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

zip stops at the shorter iterator, so a short MDS row now hashes successfully with terms silently dropped instead of panicking — an empty first row makes hash return Ok(0).

Suggest a check in PoseidonParameters::new: reject unless mds.len() == width and every row is width long. Add regressions for an empty row and a width - 1 row.

Generated with Claude Code

Indexing the MDS matrix as `mds[i][j]` bounds-checks and chases the row
pointer on every inner iteration. Zipping the state iterator with the
row slice removes both and improves performance.

Before:

```
poseidon_bn254_x5_1     time:   [23.550 µs 23.586 µs 23.629 µs]
poseidon_bn254_x5_2     time:   [33.843 µs 33.914 µs 33.990 µs]
poseidon_bn254_x5_3     time:   [45.972 µs 46.023 µs 46.080 µs]
poseidon_bn254_x5_4     time:   [64.794 µs 64.868 µs 64.950 µs]
poseidon_bn254_x5_5     time:   [83.945 µs 84.056 µs 84.183 µs]
poseidon_bn254_x5_6     time:   [111.81 µs 111.93 µs 112.08 µs]
poseidon_bn254_x5_7     time:   [143.60 µs 143.76 µs 143.94 µs]
poseidon_bn254_x5_8     time:   [174.56 µs 175.58 µs 176.93 µs]
poseidon_bn254_x5_9     time:   [204.46 µs 204.78 µs 205.15 µs]
poseidon_bn254_x5_10    time:   [271.73 µs 272.08 µs 272.48 µs]
poseidon_bn254_x5_11    time:   [296.99 µs 297.33 µs 297.74 µs]
poseidon_bn254_x5_12    time:   [376.47 µs 376.79 µs 377.26 µs]
```

After:

```
poseidon_bn254_x5_1     time:   [23.066 µs 23.174 µs 23.325 µs]
                        change: [-2.2081% -1.8589% -1.3974%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 10 outliers among 100 measurements (10.00%)
  6 (6.00%) high mild
  4 (4.00%) high severe

poseidon_bn254_x5_2     time:   [32.838 µs 32.869 µs 32.904 µs]
                        change: [-3.0838% -2.9449% -2.8031%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
  3 (3.00%) high mild
  4 (4.00%) high severe

poseidon_bn254_x5_3     time:   [44.638 µs 44.666 µs 44.696 µs]
                        change: [-3.1142% -2.9264% -2.7627%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 3 outliers among 100 measurements (3.00%)
  3 (3.00%) high mild

poseidon_bn254_x5_4     time:   [63.564 µs 63.667 µs 63.789 µs]
                        change: [-1.7487% -1.5015% -1.1982%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) high mild
  4 (4.00%) high severe

poseidon_bn254_x5_5     time:   [82.885 µs 82.967 µs 83.072 µs]
                        change: [-1.5774% -1.3842% -1.2051%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 6 outliers among 100 measurements (6.00%)
  2 (2.00%) high mild
  4 (4.00%) high severe

poseidon_bn254_x5_6     time:   [110.84 µs 110.96 µs 111.10 µs]
                        change: [-1.0675% -0.9438% -0.8197%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) high mild
  2 (2.00%) high severe

poseidon_bn254_x5_7     time:   [142.01 µs 142.19 µs 142.39 µs]
                        change: [-1.2473% -1.0711% -0.9154%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 5 outliers among 100 measurements (5.00%)
  4 (4.00%) high mild
  1 (1.00%) high severe

poseidon_bn254_x5_8     time:   [173.71 µs 173.91 µs 174.11 µs]
                        change: [-0.9042% -0.5522% -0.2606%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high mild

poseidon_bn254_x5_9     time:   [204.29 µs 205.15 µs 206.12 µs]
                        change: [-0.0241% +0.5845% +1.2478%] (p = 0.06 > 0.05)
                        No change in performance detected.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high mild

poseidon_bn254_x5_10    time:   [267.99 µs 268.63 µs 269.34 µs]
                        change: [-1.3121% -0.9879% -0.7280%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) high mild

poseidon_bn254_x5_11    time:   [292.10 µs 292.62 µs 293.23 µs]
                        change: [-1.6042% -1.4175% -1.2132%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 17 outliers among 100 measurements (17.00%)
  15 (15.00%) high mild
  2 (2.00%) high severe

poseidon_bn254_x5_12    time:   [369.39 µs 370.32 µs 371.35 µs]
                        change: [-2.6819% -2.0366% -1.6086%] (p = 0.00 < 0.05)
                        Performance has improved.
```
@vadorovsky

Copy link
Copy Markdown
Contributor Author

zip stops at the shorter iterator, so a short MDS row now hashes successfully with terms silently dropped instead of panicking — an empty first row makes hash return Ok(0).

I don't think this is ever going to happen. state has width elements. If you look at our parameters, each row of mds also has width elements.

In https://extgit.isec.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_params_poseidon.sage?ref_type=heads, NUM_CELLS is used both for t:
https://extgit.isec.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_params_poseidon.sage?ref_type=heads#L325
and for MDS:
https://extgit.isec.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_params_poseidon.sage?ref_type=heads#L457

Besides, if there was ever a situation where MDS row is shorter than the state, the current code that indexes into MDS (self.params.mds[i][j]) would panic.

Suggest a check in PoseidonParameters::new: reject unless mds.len() == width and every row is width long. Add regressions for an empty row and a width - 1 row.

My goal here is to make the syscall as fast as possible, I feel like adding an another check (especially of low practical value) goes against it.

Something that I would me more accepting of would be adding such a check in xtask for generating parameters (although I would be also fine with not checking that at all and trusting the algorithm for generating parameters).

@ananas-block

ananas-block commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Two additional ideas, both on the syscall hot path.

  1. Parameter construction runs on every syscall. solana-poseidon's hashv calls Poseidon::<Fr>::new_circom(vals.len()) per invocation (lib.rs:253), and get_poseidon_parameters emits every constant as F::from(BigInteger256::new([...])) — a runtime Montgomery multiply by R2, ~132 at width 2, ~1050 at width 13. Fp::new is a const fn doing the same reduction at compile time; same limbs, no constant regeneration. Measured 2.75 us of a 9.47 us width-2 round trip (29%). Needs the generated signature specialized from generic F to Fr — edit the xtask template, not the 43k-line output.

  2. apply_mds allocates a Vec per round. self.state = ...collect() runs once per round: 65 allocations per width-2 hash, 73 per width-13. Field: Copy + Default, so a [F; MAX_X5_LEN] stack temp plus copy_from_slice removes all of them — 416 bytes of frame at width 13.

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.

2 participants