From 7bde69f0ce9ad0647f618db270ab861779ed48d6 Mon Sep 17 00:00:00 2001 From: floor-licker Date: Mon, 22 Jun 2026 17:31:49 -0400 Subject: [PATCH] feat: use simd json for rest market parsing --- README.md | 6 +-- .../official_client_side_by_side_benchmark.rs | 15 ++++--- src/client.rs | 42 +++++++++---------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index b82e10e..970f81f 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Real-world Polymarket API latency broken down by request phase: - **32.5% more consistent** - **4.2x faster** than Official Python Client -**Benchmark Methodology:** The `rs-clob-client-v2` comparison separates cold start, warm connection, steady-state typed requests, network-only byte fetches, and CPU-only parsing. The latest local live-network run was on June 22, 2026 against `https://clob.polymarket.com/simplified-markets?next_cursor=MA==`. Steady-state rows use 40 paired iterations with alternating order and 100ms delay after 5 warmups; parse rows use 300 iterations from a cached 480KB payload. The network-only row compares byte fetches through each HTTP stack without typed deserialization. The CPU parse row compares polyfill typed parsing against the `rs-clob-client-v2` request-helper parse path; direct serde parsing of the SDK response type measured 0.5 / 0.6 / 0.6 ms. Run it with `cargo run --release --example official_client_side_by_side_benchmark --features official-client-benchmark`. See `examples/side_by_side_benchmark.rs` in commit `a63a170`: https://github.com/floor-licker/polyfill-rs/blob/a63a170/examples/side_by_side_benchmark.rs for the original legacy benchmark implementation. +**Benchmark Methodology:** The `rs-clob-client-v2` comparison separates cold start, warm connection, steady-state typed requests, network-only byte fetches, and CPU-only parsing. The latest local live-network run was on June 22, 2026 against `https://clob.polymarket.com/simplified-markets?next_cursor=MA==`. Steady-state rows use 40 paired iterations with alternating order and 100ms delay after 5 warmups; parse rows use 300 iterations from a cached 480KB payload. The network-only row compares byte fetches through each HTTP stack without typed deserialization. The CPU parse row compares polyfill's SIMD-backed typed parser against the `rs-clob-client-v2` request-helper parse path; direct serde parsing of the SDK response type measured 0.5 / 0.6 / 0.6 ms. Run it with `cargo run --release --example official_client_side_by_side_benchmark --features official-client-benchmark`. See `examples/side_by_side_benchmark.rs` in commit `a63a170`: https://github.com/floor-licker/polyfill-rs/blob/a63a170/examples/side_by_side_benchmark.rs for the original legacy benchmark implementation. **Computational Performance (pure CPU, no I/O)** @@ -70,14 +70,14 @@ Real-world Polymarket API latency broken down by request phase: |-----------|-------------|-------| | **Order Book Updates (1000 ops)** | 159.6 µs ± 32 µs | 6,260 updates/sec, zero-allocation | | **Spread/Mid Calculations** | 70 ns ± 77 ns | 14.3M ops/sec, optimized BTreeMap | -| **JSON Parsing (480KB)** | ~2.3 ms | SIMD-accelerated parsing (1.77x faster than serde_json) | +| **JSON Parsing (480KB)** | ~0.5 ms | SIMD-backed parsing for large REST market responses and benchmarked polyfill typed parse path | | **WS `book` hot path (decode + apply)** | ~0.23 µs / 1.73 µs / 6.74 µs | 1 / 16 / 64 levels-per-side, strict fixed-point tape parser with generation-marked snapshot retention (see `benches/ws_hot_path.rs`) | Run the WS hot-path benchmark locally with `cargo bench --bench ws_hot_path`. **Key Performance Optimizations:** -The 21.4% performance improvement comes from SIMD-accelerated JSON parsing (1.77x faster than serde_json), HTTP/2 tuning with 512KB stream windows optimized for 469KB payloads, explicit Polymarket request headers, and opt-in connection prewarming/keep-alive support. +The 21.4% performance improvement comes from HTTP/2 tuning with 512KB stream windows optimized for 469KB payloads, explicit Polymarket request headers, SIMD-backed parsing for large REST market responses, and opt-in connection prewarming/keep-alive support. ### Memory Architecture diff --git a/examples/official_client_side_by_side_benchmark.rs b/examples/official_client_side_by_side_benchmark.rs index 4ebb09e..961c4e9 100644 --- a/examples/official_client_side_by_side_benchmark.rs +++ b/examples/official_client_side_by_side_benchmark.rs @@ -21,6 +21,8 @@ use polymarket_client_sdk_v2::clob::types::response::{ }; use polymarket_client_sdk_v2::clob::{Client as OfficialClient, Config as OfficialConfig}; +type BenchResult = Result>; + const INITIAL_CURSOR: &str = "MA=="; const OFFICIAL_SDK_REV: &str = "8ba5008733c3c03e92041eef8b1cb8495dbed718"; @@ -323,21 +325,22 @@ fn official_style_http_client() -> Result { reqwest::Client::builder().default_headers(headers).build() } -fn parse_polyfill_once(bytes: &[u8]) -> Result { +fn parse_polyfill_once(bytes: &[u8]) -> BenchResult { let start = Instant::now(); - let page: polyfill_rs::types::SimplifiedMarketsResponse = serde_json::from_slice(bytes)?; + let page: polyfill_rs::types::SimplifiedMarketsResponse = + polyfill_rs::decode::fast_parse::parse_json_fast_owned(bytes)?; black_box(page); Ok(start.elapsed()) } -fn parse_official_direct_once(bytes: &[u8]) -> Result { +fn parse_official_direct_once(bytes: &[u8]) -> BenchResult { let start = Instant::now(); let page: OfficialPage = serde_json::from_slice(bytes)?; black_box(page); Ok(start.elapsed()) } -fn parse_official_helper_once(bytes: &[u8]) -> Result { +fn parse_official_helper_once(bytes: &[u8]) -> BenchResult { let start = Instant::now(); let value: serde_json::Value = serde_json::from_slice(bytes)?; let page: Option> = @@ -350,9 +353,9 @@ fn run_parse_samples( bytes: &[u8], iterations: usize, mut parse_once: F, -) -> Result, serde_json::Error> +) -> BenchResult> where - F: FnMut(&[u8]) -> Result, + F: FnMut(&[u8]) -> BenchResult, { let mut times = Vec::with_capacity(iterations); for _ in 0..iterations { diff --git a/src/client.rs b/src/client.rs index 1b6bfff..febbeb7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -17,9 +17,10 @@ use alloy_primitives::{Address, U256}; use alloy_signer_local::PrivateKeySigner; use reqwest::header::{HeaderMap, HeaderName, HeaderValue, ACCEPT, CONTENT_TYPE, USER_AGENT}; use reqwest::Client; -use reqwest::{Method, RequestBuilder}; +use reqwest::{Method, RequestBuilder, Response}; use rust_decimal::prelude::FromPrimitive; use rust_decimal::Decimal; +use serde::de::DeserializeOwned; use serde::Serialize; use serde_json::Value; use std::net::{IpAddr, SocketAddr}; @@ -117,6 +118,19 @@ struct ClientAuthConfig { } impl ClobClient { + async fn parse_json_response(response: Response) -> Result + where + T: DeserializeOwned, + { + let mut bytes = response + .bytes() + .await + .map_err(|e| PolyfillError::network(format!("Failed to read response body: {e}"), e))? + .to_vec(); + + crate::decode::fast_parse::parse_json_fast(&mut bytes) + } + fn build_client( host: &str, chain_id: u64, @@ -368,8 +382,7 @@ impl ClobClient { )); } - let order_book: OrderBookSummary = response.json().await?; - Ok(order_book) + Self::parse_json_response(response).await } /// Get midpoint for a token @@ -388,8 +401,7 @@ impl ClobClient { )); } - let midpoint: MidpointResponse = response.json().await?; - Ok(midpoint) + Self::parse_json_response(response).await } /// Get spread for a token @@ -2407,10 +2419,7 @@ impl ClobClient { .await .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?; - response - .json::() - .await - .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + Self::parse_json_response(response).await } /// Get sampling simplified markets with pagination @@ -2428,10 +2437,7 @@ impl ClobClient { .await .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?; - response - .json::() - .await - .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + Self::parse_json_response(response).await } /// Get markets with pagination @@ -2449,10 +2455,7 @@ impl ClobClient { .await .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?; - response - .json::() - .await - .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + Self::parse_json_response(response).await } /// Get simplified markets with pagination @@ -2470,10 +2473,7 @@ impl ClobClient { .await .map_err(|e| PolyfillError::network(format!("Request failed: {}", e), e))?; - response - .json::() - .await - .map_err(|e| PolyfillError::parse(format!("Failed to parse response: {}", e), None)) + Self::parse_json_response(response).await } /// Get single market by condition ID