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:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sol-trade-sdk"
|
name = "sol-trade-sdk"
|
||||||
version = "3.6.4"
|
version = "3.6.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = [
|
authors = [
|
||||||
"William <byteblock6@gmail.com>",
|
"William <byteblock6@gmail.com>",
|
||||||
|
|||||||
+21
-3
@@ -9,6 +9,8 @@ pub struct InfrastructureConfig {
|
|||||||
pub rpc_url: String,
|
pub rpc_url: String,
|
||||||
pub swqos_configs: Vec<SwqosConfig>,
|
pub swqos_configs: Vec<SwqosConfig>,
|
||||||
pub commitment: CommitmentConfig,
|
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 {
|
impl InfrastructureConfig {
|
||||||
@@ -17,7 +19,12 @@ impl InfrastructureConfig {
|
|||||||
swqos_configs: Vec<SwqosConfig>,
|
swqos_configs: Vec<SwqosConfig>,
|
||||||
commitment: CommitmentConfig,
|
commitment: CommitmentConfig,
|
||||||
) -> Self {
|
) -> 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)
|
/// Create from TradeConfig (extract infrastructure-only settings)
|
||||||
@@ -26,6 +33,7 @@ impl InfrastructureConfig {
|
|||||||
rpc_url: config.rpc_url.clone(),
|
rpc_url: config.rpc_url.clone(),
|
||||||
swqos_configs: config.swqos_configs.clone(),
|
swqos_configs: config.swqos_configs.clone(),
|
||||||
commitment: config.commitment.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) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
self.rpc_url.hash(state);
|
self.rpc_url.hash(state);
|
||||||
self.swqos_configs.hash(state);
|
self.swqos_configs.hash(state);
|
||||||
// Hash commitment level as string since CommitmentConfig doesn't impl Hash
|
|
||||||
format!("{:?}", self.commitment).hash(state);
|
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.rpc_url == other.rpc_url
|
||||||
&& self.swqos_configs == other.swqos_configs
|
&& self.swqos_configs == other.swqos_configs
|
||||||
&& self.commitment == other.commitment
|
&& 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,
|
pub log_enabled: bool,
|
||||||
/// Whether to check minimum tip per SWQOS provider (filter out configs below min). Default false to save latency.
|
/// Whether to check minimum tip per SWQOS provider (filter out configs below min). Default false to save latency.
|
||||||
pub check_min_tip: bool,
|
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 {
|
impl TradeConfig {
|
||||||
@@ -91,7 +102,8 @@ impl TradeConfig {
|
|||||||
create_wsol_ata_on_startup: true, // default: check and create on startup
|
create_wsol_ata_on_startup: true, // default: check and create on startup
|
||||||
use_seed_optimize: true, // default: use seed optimization
|
use_seed_optimize: true, // default: use seed optimization
|
||||||
log_enabled: true, // default: enable all SDK logs
|
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.check_min_tip = check_min_tip;
|
||||||
self
|
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;
|
pub type SolanaRpcClient = solana_client::nonblocking::rpc_client::RpcClient;
|
||||||
|
|||||||
+29
-1
@@ -185,7 +185,15 @@ impl TradingInfrastructure {
|
|||||||
let max_by_cores = (num_cores * 2 / 3).max(1);
|
let max_by_cores = (num_cores * 2 / 3).max(1);
|
||||||
let cap = swqos_count.min(max_by_cores).max(1);
|
let cap = swqos_count.min(max_by_cores).max(1);
|
||||||
let ids = core_affinity::get_core_ids()
|
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();
|
.unwrap_or_default();
|
||||||
(cap, Arc::new(ids))
|
(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
|
/// Main trading client for Solana DeFi protocols
|
||||||
///
|
///
|
||||||
/// `SolTradingSDK` provides a unified interface for trading across multiple Solana DEXs
|
/// `SolTradingSDK` provides a unified interface for trading across multiple Solana DEXs
|
||||||
@@ -636,6 +661,9 @@ impl TradingClient {
|
|||||||
/// - `None`: keep default (shared tokio pool).
|
/// - `None`: keep default (shared tokio pool).
|
||||||
/// - `Some(vec![])`: dedicated threads with default count, no core pinning.
|
/// - `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 {
|
pub fn with_dedicated_sender_threads(mut self, core_indices: Option<Vec<usize>>) -> Self {
|
||||||
match core_indices {
|
match core_indices {
|
||||||
None => {
|
None => {
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
//! - **Dedicated threads** (opt-in via `with_dedicated_sender_threads`): N OS threads run sender work only, optionally pinned to cores.
|
//! - **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).
|
//! - **Arc**: Shared data behind `Arc` → clone = refcount increment (no data copy).
|
||||||
//! - **Refs**: `build_transaction` takes refs only; worker path avoids extra clones.
|
//! - **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 anyhow::{anyhow, Result};
|
||||||
use crossbeam_queue::ArrayQueue;
|
use crossbeam_queue::ArrayQueue;
|
||||||
|
|||||||
Reference in New Issue
Block a user