feat: use simd json for rest market parsing

This commit is contained in:
floor-licker
2026-06-22 17:31:49 -04:00
parent 91b1f9f463
commit 7bde69f0ce
3 changed files with 33 additions and 30 deletions
+3 -3
View File
@@ -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
@@ -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<T> = Result<T, Box<dyn std::error::Error>>;
const INITIAL_CURSOR: &str = "MA==";
const OFFICIAL_SDK_REV: &str = "8ba5008733c3c03e92041eef8b1cb8495dbed718";
@@ -323,21 +325,22 @@ fn official_style_http_client() -> Result<reqwest::Client, reqwest::Error> {
reqwest::Client::builder().default_headers(headers).build()
}
fn parse_polyfill_once(bytes: &[u8]) -> Result<Duration, serde_json::Error> {
fn parse_polyfill_once(bytes: &[u8]) -> BenchResult<Duration> {
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<Duration, serde_json::Error> {
fn parse_official_direct_once(bytes: &[u8]) -> BenchResult<Duration> {
let start = Instant::now();
let page: OfficialPage<OfficialSimplifiedMarketResponse> = serde_json::from_slice(bytes)?;
black_box(page);
Ok(start.elapsed())
}
fn parse_official_helper_once(bytes: &[u8]) -> Result<Duration, serde_json::Error> {
fn parse_official_helper_once(bytes: &[u8]) -> BenchResult<Duration> {
let start = Instant::now();
let value: serde_json::Value = serde_json::from_slice(bytes)?;
let page: Option<OfficialPage<OfficialSimplifiedMarketResponse>> =
@@ -350,9 +353,9 @@ fn run_parse_samples<F>(
bytes: &[u8],
iterations: usize,
mut parse_once: F,
) -> Result<Vec<Duration>, serde_json::Error>
) -> BenchResult<Vec<Duration>>
where
F: FnMut(&[u8]) -> Result<Duration, serde_json::Error>,
F: FnMut(&[u8]) -> BenchResult<Duration>,
{
let mut times = Vec::with_capacity(iterations);
for _ in 0..iterations {
+21 -21
View File
@@ -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<T>(response: Response) -> Result<T>
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::<crate::types::MarketsResponse>()
.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::<crate::types::SimplifiedMarketsResponse>()
.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::<crate::types::MarketsResponse>()
.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::<crate::types::SimplifiedMarketsResponse>()
.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