Release v3.6.5: SWQOS core affinity and recommended sender thread indices

- Add TradeConfig::with_swqos_cores_from_end(bool) to use last N CPU cores for SWQOS, reducing contention with main thread and default tokio workers.
- Add recommended_sender_thread_core_indices(swqos_count) to get the same last-N core indices for with_dedicated_sender_threads (recommended combo for lower latency).
- Document core affinity and latency in async_executor and with_dedicated_sender_threads.

Made-with: Cursor
This commit is contained in:
Wood
2026-03-17 17:19:04 +08:00
parent 624b1843a2
commit adfa9e1115
4 changed files with 57 additions and 6 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "sol-trade-sdk"
version = "3.6.4"
version = "3.6.5"
edition = "2021"
authors = [
"William <byteblock6@gmail.com>",
+21 -3
View File
@@ -9,6 +9,8 @@ pub struct InfrastructureConfig {
pub rpc_url: String,
pub swqos_configs: Vec<SwqosConfig>,
pub commitment: CommitmentConfig,
/// When true, SWQOS sender threads use the *last* N cores instead of the first N. Reduces contention with main thread / default tokio workers that often use low-numbered cores. Default false.
pub swqos_cores_from_end: bool,
}
impl InfrastructureConfig {
@@ -17,7 +19,12 @@ impl InfrastructureConfig {
swqos_configs: Vec<SwqosConfig>,
commitment: CommitmentConfig,
) -> Self {
Self { rpc_url, swqos_configs, commitment }
Self {
rpc_url,
swqos_configs,
commitment,
swqos_cores_from_end: false,
}
}
/// Create from TradeConfig (extract infrastructure-only settings)
@@ -26,6 +33,7 @@ impl InfrastructureConfig {
rpc_url: config.rpc_url.clone(),
swqos_configs: config.swqos_configs.clone(),
commitment: config.commitment.clone(),
swqos_cores_from_end: config.swqos_cores_from_end,
}
}
@@ -43,8 +51,8 @@ impl Hash for InfrastructureConfig {
fn hash<H: Hasher>(&self, state: &mut H) {
self.rpc_url.hash(state);
self.swqos_configs.hash(state);
// Hash commitment level as string since CommitmentConfig doesn't impl Hash
format!("{:?}", self.commitment).hash(state);
self.swqos_cores_from_end.hash(state);
}
}
@@ -53,6 +61,7 @@ impl PartialEq for InfrastructureConfig {
self.rpc_url == other.rpc_url
&& self.swqos_configs == other.swqos_configs
&& self.commitment == other.commitment
&& self.swqos_cores_from_end == other.swqos_cores_from_end
}
}
@@ -72,6 +81,8 @@ pub struct TradeConfig {
pub log_enabled: bool,
/// Whether to check minimum tip per SWQOS provider (filter out configs below min). Default false to save latency.
pub check_min_tip: bool,
/// When true, SWQOS uses the *last* N cores (instead of the first N). Use when main thread / tokio use low-numbered cores to reduce CPU contention. Default false.
pub swqos_cores_from_end: bool,
}
impl TradeConfig {
@@ -91,7 +102,8 @@ impl TradeConfig {
create_wsol_ata_on_startup: true, // default: check and create on startup
use_seed_optimize: true, // default: use seed optimization
log_enabled: true, // default: enable all SDK logs
check_min_tip: false, // default: skip min tip check to reduce latency
check_min_tip: false, // default: skip min tip check to reduce latency
swqos_cores_from_end: false,
}
}
@@ -111,6 +123,12 @@ impl TradeConfig {
self.check_min_tip = check_min_tip;
self
}
/// Use the *last* N cores for SWQOS (instead of the first N). Call this when the main thread or tokio workers use low-numbered cores to avoid binding SWQOS to busy cores. Default false.
pub fn with_swqos_cores_from_end(mut self, from_end: bool) -> Self {
self.swqos_cores_from_end = from_end;
self
}
}
pub type SolanaRpcClient = solana_client::nonblocking::rpc_client::RpcClient;
+30 -2
View File
@@ -185,7 +185,15 @@ impl TradingInfrastructure {
let max_by_cores = (num_cores * 2 / 3).max(1);
let cap = swqos_count.min(max_by_cores).max(1);
let ids = core_affinity::get_core_ids()
.map(|all| all.into_iter().take(cap).collect::<Vec<_>>())
.map(|all| {
let v: Vec<_> = all.into_iter().collect();
let len = v.len();
if config.swqos_cores_from_end && len >= cap {
v.into_iter().skip(len - cap).collect()
} else {
v.into_iter().take(cap).collect()
}
})
.unwrap_or_default();
(cap, Arc::new(ids))
};
@@ -200,6 +208,23 @@ impl TradingInfrastructure {
}
}
/// When using `TradeConfig::with_swqos_cores_from_end(true)`, returns the same "last N" core indices
/// that the infrastructure uses. Pass the result to `TradingClient::with_dedicated_sender_threads`
/// for 方式 C (组合使用): SWQOS on last N cores and dedicated sender threads pinned to those cores.
///
/// Returns `None` if core count cannot be determined. `swqos_count` is typically `swqos_configs.len()`.
pub fn recommended_sender_thread_core_indices(swqos_count: usize) -> Option<Vec<usize>> {
let all = core_affinity::get_core_ids()?;
let num_cores = all.len();
if num_cores == 0 {
return None;
}
let max_by_cores = (num_cores * 2 / 3).max(1);
let cap = swqos_count.min(max_by_cores).max(1).min(num_cores);
let start = num_cores.saturating_sub(cap);
Some((start..num_cores).collect())
}
/// Main trading client for Solana DeFi protocols
///
/// `SolTradingSDK` provides a unified interface for trading across multiple Solana DEXs
@@ -635,7 +660,10 @@ impl TradingClient {
/// Concurrency and core count are capped internally (≤ swqos count, ≤ 2/3 of CPU cores).
/// - `None`: keep default (shared tokio pool).
/// - `Some(vec![])`: dedicated threads with default count, no core pinning.
/// - `Some(indices)`: dedicated threads pinned to those core indices (trimmed to cap).
/// - `Some(indices)`: dedicated threads pinned to those core indices (trimmed to cap).
///
/// **Latency note:** If a core is busy with other work (node, bot), SWQOS submit on that core can be delayed.
/// For lowest latency, pass core indices that are *reserved* for SWQOS (do not run other CPU-heavy work on those cores).
pub fn with_dedicated_sender_threads(mut self, core_indices: Option<Vec<usize>>) -> Self {
match core_indices {
None => {
+5
View File
@@ -5,6 +5,11 @@
//! - **Dedicated threads** (opt-in via `with_dedicated_sender_threads`): N OS threads run sender work only, optionally pinned to cores.
//! - **Arc**: Shared data behind `Arc` → clone = refcount increment (no data copy).
//! - **Refs**: `build_transaction` takes refs only; worker path avoids extra clones.
//!
//! **Core affinity & latency:** Each job is assigned a core (round-robin from `effective_core_ids`). When a worker runs a job,
//! it sets thread affinity to that core. If that core is busy with other work (e.g. node sync, bot logic), SWQOS submit on that
//! core will compete for CPU and latency can increase. For lowest latency, reserve a subset of cores for SWQOS only via
//! `with_dedicated_sender_threads(Some(indices))` and avoid running other CPU-heavy work on those core indices.
use anyhow::{anyhow, Result};
use crossbeam_queue::ArrayQueue;