Skip to content

Share v3 tick maps via Arc to cut per-solve copies#4566

Open
jmg-duarte wants to merge 4 commits into
mainfrom
jmgd/v3-pool-i128-arc
Open

Share v3 tick maps via Arc to cut per-solve copies#4566
jmg-duarte wants to merge 4 commits into
mainfrom
jmgd/v3-pool-i128-arc

Conversation

@jmg-duarte

@jmg-duarte jmg-duarte commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Description

Share the Uniswap V3 liquidity_net tick map via Arc instead of deep-copying
it on every /solve (once into the driver domain pool, once per solver request).
The optimization relies on forcing the scalar types over wrappers due to Rust not being able to "see through" cross crate boundaries (driver/solver-dto), with the scalars it can do so and properly avoid a full clone.

Changes

  • PoolState.liquidity_netArc<BTreeMap<i32, i128>>, mutated copy-on-write
    via Arc::make_mut on Mint/Burn.
  • Driver domain Pool.liquidity_netArc<BTreeMap<i32, i128>>; removed the
    now-unused LiquidityNet newtype.
  • solvers-dto ConcentratedLiquidityPool.liquidity_net: HashMap
    Arc<BTreeMap<i32, i128>>.

How to test

Tested in staging, spikes are orders I placed, one can see that the size of the spike reduces considerably

image

The UniswapV3 `liquidity_net` tick map was deep-copied multiple times per
`/solve`, dominating driver heap churn:

- `append_events` does `Arc::make_mut` on each event-affected `PoolInfo`,
  cloning the whole map even for Swap events (the common case) that only
  touch scalar state and never read the map.
- `to_domain` rebuilt a fresh `BTreeMap` for every relevant pool on every
  request when converting the source pool into the driver domain type.

Wrap `liquidity_net` in `Arc<BTreeMap<i32, i128>>` end-to-end (source
`PoolState` and domain `Pool`). Now:

- Swap events share the map (no copy); Mint/Burn copy-on-write only when
  they actually mutate ticks.
- `to_domain` clones the `Arc` instead of rebuilding the map.

The map is write-once / read-once in the domain (built in `to_domain`,
read only when serializing the solver request), so the previously distinct
`Tick`/`LiquidityNet`-keyed domain map is now the shared source map; the
unused `LiquidityNet` newtype is removed. Wire format is unchanged
(serde_with serializes the `Arc` transparently; `encode_decode_pool_info`
still passes) and the event arithmetic is unchanged (append_events tests
pass).
@jmg-duarte jmg-duarte force-pushed the jmgd/v3-pool-i128-arc branch from 4b334ee to f928120 Compare June 26, 2026 15:50
@jmg-duarte jmg-duarte marked this pull request as ready for review June 26, 2026 16:12
@jmg-duarte jmg-duarte requested a review from a team as a code owner June 26, 2026 16:12
@claude

claude Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Claude finished @jmg-duarte's task in 2m 26s —— View job


PR Review: Share v3 tick maps via Arc

  • Read changed files & gather context
  • Analyze correctness & performance of Arc changes
  • Trace all consumers of the changed types
  • Post review

LGTM. This is a clean, well-scoped optimization. I traced the full data flow and found no blocking issues.

Correctness verified:

  • COW semantics are preserved. append_events already did Arc::make_mut on the outer Arc<PoolInfo>; the new inner Arc::make_mut(&mut pool.liquidity_net) correctly copies-on-write only when the tick map is actually shared. When nothing else holds the map, both mutations are copy-free — exactly the intended win. Behavior on Mint/Burn is unchanged.
  • #[derive(Eq, PartialEq)] on PoolState still compares map contents (Arc's PartialEq derefs), so any equality-based dedup/caching of PoolInfo keeps working.
  • Wire format is unchanged by HashMapBTreeMap in solvers-dto::ConcentratedLiquidityPool: both serialize as a JSON object of string→string via DisplayFromStr. As a bonus, BTreeMap makes key ordering deterministic. The serde_as(as = "Arc<BTreeMap<...>>") adapter is supported by serde_with.
  • No orphaned consumers. The removed LiquidityNet/Tick-keyed map had only two readers (boundary construction and infra/solver/dto/auction.rs), both now .clone() the Arc cheaply. The internal solvers engine deserializes the field but doesn't read it, so no behavior change there.

Minor (non-blocking) observation: the domain Pool.liquidity_net drops the Tick/LiquidityNet newtypes in favor of raw i32/i128. This is the necessary trade-off to share the Arc with the source without re-collecting, so it's the right call here — just noting the small loss of domain-level type safety.

I couldn't run cargo check/clippy in this environment (command not permitted), so I'm relying on CI for the final compile/lint — but the changes look type-correct on inspection, and the author reports staging verification.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request optimizes Uniswap V3 pool data structures by wrapping the liquidity_net tick map in an Arc (Arc<BTreeMap<i32, i128>>) across domain, boundary, and DTO layers. This avoids expensive deep-copying of the tick map during solver requests, utilizing copy-on-write via Arc::make_mut when updating liquidity net values during mint or burn events. No critical issues were found, and no additional feedback is provided.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@squadgazzz

Copy link
Copy Markdown
Contributor

Any idea why the overall memory consumption is higher on the second run?

@jmg-duarte

jmg-duarte commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Any idea why the overall memory consumption is higher on the second run?

IIRC, the green run had quite a bit of time running before, and the allocator might've add more aggressive clean up settings (lower decay) but there's one more trick to improve the situation (needs testing, but I'll open the PR ASAP)

@MartinquaXD MartinquaXD left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

noice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants