Skip to content

Describe the shipped no-alloc mechanism and measured gas behaviour instead of stale figures - #35

Merged
thedavidmeister merged 2 commits into
mainfrom
2026-09-03-issue-25-gas-claims-and-no-alloc-strategy
Sep 3, 2026
Merged

thedavidmeister merged 2 commits into
mainfrom
2026-09-03-issue-25-gas-claims-and-no-alloc-strategy

Conversation

@thedavidmeister

Copy link
Copy Markdown
Contributor

Closes #25

What changed

Six sentences, nothing else: src/LibHashNoAlloc.sol title block L39-45, L47-49, L65-67 and the hashBytes summary line L69; README.md L188-190 and L440-442. Numbers were replaced by the mechanisms that produce them (gas figures drift with compiler settings; mechanisms do not), and each mechanism was re-measured in this clone under the CI harness before being written down.

Issue claim Was Now
3. "write the words after the free memory pointer ... pay for memory expansion ... call this kind of expansion 'no alloc'" (L37-45) Describes a technique no function uses combineHashes writes the two words to scratch 0x00-0x3f and hashes there; neither 0x40 nor anything past it is touched, so nothing is allocated and no expansion is paid. "No alloc" = hashBytes/hashWords hash in place, combineHashes hashes through scratch. L37-38 ("Worst case ... in which") kept byte-identical; only the mechanism sentences change.
4b. "the gas saving ... can be lost by the overhead of jumps and stack manipulation due to function calls" (L47-49) Overhead presented as able to erase the saving Where the optimizer does not inline the internal call, the jump + stack shuffle costs tens of gas (measured 21), well under the saving on even one or two words (85+ gas).
1. "about 70 gas vs. about 350 gas" (L65-67) 350 is 1.5x the measured 235 In-place hash = one keccak256 + stack ops; keccak256(abi.encode(foo_)) allocates a 3-word buffer, copies the fields, bumps the free memory pointer, then pays the same hash. The encoding step alone (172) costs more than the whole in-place hash (68).
4a. "Hash bytes without allocating memory." (L69) Implies a saving over keccak256(bytes) Solidity's keccak256(data) already compiles to the same keccak256(add(data, 0x20), mload(data)); hashBytes saves nothing over it and exists for API symmetry.
2. "the no-encode solution often costs 40-80%+ less gas" (README L188-190) Fixed band, unreached by the README's own fold at the sizes the issue measured Saving = encoding's allocate-and-copy cost (grows with the input) minus the fixed extra work of the no-encode form (e.g. extra keccak256 calls): slightly negative for a few words, most of the gas for larger inputs.
2. "seems to be about 40% cheaper ... based on some simple testing" (README L440-442) One unstated data shape Saving depends on data per element, not element count: fold pays ~fixed cost per keccak256 call (6 per Foo) + 6 gas/word; abi.encode copies every word + head/tail bookkeeping. Measured envelope stated in words (about the same or slightly more with empty fields; a third to nearly half less with a handful of words; about a fifth at ~1 KB; falling further).

Measurements (this clone, CI harness: solc 0.8.25, optimizer 100000 runs, cancun; gasleft() deltas, every measured result asserted equal to its counterpart so the optimizer cannot drop it)

Foo3 {uint256, address, uint32}:  keccak256(foo_, 0x60) 68 | abi.encode(foo_) alone 172 | keccak256(abi.encode(foo_)) 235 | keccak256(abi.encode(a,b,c)) 259
hashBytes vs inline asm vs keccak256(bytes):  0B 57/57/57  32B 63/63/63  512B 153/153/153
library call vs identical inline asm:  combineHashes 102 vs 81 (+21)  hashWords 0/1/20 words 90/96/210 vs 69/75/189 (+21)  hashBytes +0
still vs abi:  combineHashes 102 vs abi.encode(a,b) 187 / encodePacked 190;  hashWords(1) 96 vs 372 / 316

nil-seeded Foo[] fold vs keccak256(abi.encode(foos)); Foo = {uint256, address, uint256[inner], bytes[inner*7]}
inner=0    n=1   683 vs   818 (83%)   n=3  1957 vs  1833 (106%)   n=10  6416 vs   5425 (118%)   fold costs MORE
inner=4    n=1   713 vs  1255 (56%)   n=3  2047 vs  3138 (65%)    n=10  6716 vs   9834 (68%)    32-44% cheaper  (the issue's rows)
inner=32   n=1   917 vs  4290 (21%)   n=3  2659 vs 12367 (21%)    n=10  8756 vs  41961 (20%)    ~80% cheaper
inner=128  n=1  1619 vs 15654 (10%)   n=3  4765 vs 47920 (9%)     n=10 15776 vs 176543 (8%)     ~90% cheaper

The "40-80%+" band is real but a function of data per element; the README's "about 40%" was one point (inner≈4) on that curve. Measurement sources: the campaign's test/adv/Gas.t.sol plus a lib-vs-inline / varying-inner companion, run against this branch; not committed because gas figures are settings-dependent and the gas tests are #26's scope.

QA

  • Discriminating tests: n/a — docs-only PR; the drifting figures were removed rather than pinned (a pinned figure would re-create the defect at the next compiler bump), and the test file is Assertion-free gas tests measure dispatcher position, not hashing (identical bodies span 217-262 gas), and nothing checks the committed .gas-snapshot #26's scope. Every sentence written is backed by the measurement table above, re-run in this clone.
  • Mutations applied: n/a — no code or test changes.
  • Oracle: gasleft() deltas under the repo's own foundry.toml settings with the measured expression's result asserted equal to its counterpart; EVM gas schedule (KECCAK256 = 30 + 6/word); Solidity memory layout (0x00-0x3f scratch, 0x40 free memory pointer, keccak256(bytes memory) lowering).
  • Category check: issue asks claims 1 (src L65-67), 2 (README L188-190, L440-442), 3 (src L37-45), 4 (src L69 and the L47-49 concession); covered all six sentences, nothing outside them.

Checks run locally in the CI harness: forge fmt --check, forge test (22 passed), slither . (0 results), rainix-sol-single-contract, reuse lint — all green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FvfmeRQKubdbFW1GL3kmib

…stead of stale figures

The title block said combineHashes writes after the free memory pointer and
pays for expansion; it hashes through scratch space 0x00-0x3f and touches
neither the pointer nor any memory past it. The "~70 vs ~350 gas" figures and
the README's "40-80%+" / "about 40%" bands are replaced by the mechanisms that
produce the saving (allocate-and-copy vs hash in place; per-keccak fixed cost
vs per-word copy) and the measured shape of the fold's saving, which depends
on data per element rather than element count. hashBytes is documented as
identical to what Solidity emits for keccak256(bytes) rather than as a
saving, and the function-call concession now states the measured overhead's
size relative to the saving.

Closes #25

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvfmeRQKubdbFW1GL3kmib
@thedavidmeister thedavidmeister self-assigned this Sep 3, 2026
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 23 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: 6ccb808f-18f0-4d02-928c-76ca09a85809

📥 Commits

Reviewing files that changed from the base of the PR and between a66cdf1 and c757459.

📒 Files selected for processing (2)
  • README.md
  • src/LibHashNoAlloc.sol

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thedavidmeister

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…rategy

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvfmeRQKubdbFW1GL3kmib

# Conflicts:
#	src/LibHashNoAlloc.sol
@thedavidmeister
thedavidmeister merged commit 8158afc into main Sep 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant