Skip to content

Commit 8c4e3ee

Browse files
committed
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
1 parent 624b184 commit 8c4e3ee

4 files changed

Lines changed: 57 additions & 6 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sol-trade-sdk"
3-
version = "3.6.4"
3+
version = "3.6.5"
44
edition = "2021"
55
authors = [
66
"William <byteblock6@gmail.com>",

src/common/types.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub struct InfrastructureConfig {
99
pub rpc_url: String,
1010
pub swqos_configs: Vec<SwqosConfig>,
1111
pub commitment: CommitmentConfig,
12+
/// 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.
13+
pub swqos_cores_from_end: bool,
1214
}
1315

1416
impl InfrastructureConfig {
@@ -17,7 +19,12 @@ impl InfrastructureConfig {
1719
swqos_configs: Vec<SwqosConfig>,
1820
commitment: CommitmentConfig,
1921
) -> Self {
20-
Self { rpc_url, swqos_configs, commitment }
22+
Self {
23+
rpc_url,
24+
swqos_configs,
25+
commitment,
26+
swqos_cores_from_end: false,
27+
}
2128
}
2229

2330
/// Create from TradeConfig (extract infrastructure-only settings)
@@ -26,6 +33,7 @@ impl InfrastructureConfig {
2633
rpc_url: config.rpc_url.clone(),
2734
swqos_configs: config.swqos_configs.clone(),
2835
commitment: config.commitment.clone(),
36+
swqos_cores_from_end: config.swqos_cores_from_end,
2937
}
3038
}
3139

@@ -43,8 +51,8 @@ impl Hash for InfrastructureConfig {
4351
fn hash<H: Hasher>(&self, state: &mut H) {
4452
self.rpc_url.hash(state);
4553
self.swqos_configs.hash(state);
46-
// Hash commitment level as string since CommitmentConfig doesn't impl Hash
4754
format!("{:?}", self.commitment).hash(state);
55+
self.swqos_cores_from_end.hash(state);
4856
}
4957
}
5058

@@ -53,6 +61,7 @@ impl PartialEq for InfrastructureConfig {
5361
self.rpc_url == other.rpc_url
5462
&& self.swqos_configs == other.swqos_configs
5563
&& self.commitment == other.commitment
64+
&& self.swqos_cores_from_end == other.swqos_cores_from_end
5665
}
5766
}
5867

@@ -72,6 +81,8 @@ pub struct TradeConfig {
7281
pub log_enabled: bool,
7382
/// Whether to check minimum tip per SWQOS provider (filter out configs below min). Default false to save latency.
7483
pub check_min_tip: bool,
84+
/// 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.
85+
pub swqos_cores_from_end: bool,
7586
}
7687

7788
impl TradeConfig {
@@ -91,7 +102,8 @@ impl TradeConfig {
91102
create_wsol_ata_on_startup: true, // default: check and create on startup
92103
use_seed_optimize: true, // default: use seed optimization
93104
log_enabled: true, // default: enable all SDK logs
94-
check_min_tip: false, // default: skip min tip check to reduce latency
105+
check_min_tip: false, // default: skip min tip check to reduce latency
106+
swqos_cores_from_end: false,
95107
}
96108
}
97109

@@ -111,6 +123,12 @@ impl TradeConfig {
111123
self.check_min_tip = check_min_tip;
112124
self
113125
}
126+
127+
/// 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.
128+
pub fn with_swqos_cores_from_end(mut self, from_end: bool) -> Self {
129+
self.swqos_cores_from_end = from_end;
130+
self
131+
}
114132
}
115133

116134
pub type SolanaRpcClient = solana_client::nonblocking::rpc_client::RpcClient;

src/lib.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,15 @@ impl TradingInfrastructure {
185185
let max_by_cores = (num_cores * 2 / 3).max(1);
186186
let cap = swqos_count.min(max_by_cores).max(1);
187187
let ids = core_affinity::get_core_ids()
188-
.map(|all| all.into_iter().take(cap).collect::<Vec<_>>())
188+
.map(|all| {
189+
let v: Vec<_> = all.into_iter().collect();
190+
let len = v.len();
191+
if config.swqos_cores_from_end && len >= cap {
192+
v.into_iter().skip(len - cap).collect()
193+
} else {
194+
v.into_iter().take(cap).collect()
195+
}
196+
})
189197
.unwrap_or_default();
190198
(cap, Arc::new(ids))
191199
};
@@ -200,6 +208,23 @@ impl TradingInfrastructure {
200208
}
201209
}
202210

211+
/// When using `TradeConfig::with_swqos_cores_from_end(true)`, returns the same "last N" core indices
212+
/// that the infrastructure uses. Pass the result to `TradingClient::with_dedicated_sender_threads`
213+
/// for 方式 C (组合使用): SWQOS on last N cores and dedicated sender threads pinned to those cores.
214+
///
215+
/// Returns `None` if core count cannot be determined. `swqos_count` is typically `swqos_configs.len()`.
216+
pub fn recommended_sender_thread_core_indices(swqos_count: usize) -> Option<Vec<usize>> {
217+
let all = core_affinity::get_core_ids()?;
218+
let num_cores = all.len();
219+
if num_cores == 0 {
220+
return None;
221+
}
222+
let max_by_cores = (num_cores * 2 / 3).max(1);
223+
let cap = swqos_count.min(max_by_cores).max(1).min(num_cores);
224+
let start = num_cores.saturating_sub(cap);
225+
Some((start..num_cores).collect())
226+
}
227+
203228
/// Main trading client for Solana DeFi protocols
204229
///
205230
/// `SolTradingSDK` provides a unified interface for trading across multiple Solana DEXs
@@ -635,7 +660,10 @@ impl TradingClient {
635660
/// Concurrency and core count are capped internally (≤ swqos count, ≤ 2/3 of CPU cores).
636661
/// - `None`: keep default (shared tokio pool).
637662
/// - `Some(vec![])`: dedicated threads with default count, no core pinning.
638-
/// - `Some(indices)`: dedicated threads pinned to those core indices (trimmed to cap).
663+
/// - `Some(indices)`: dedicated threads pinned to those core indices (trimmed to cap).
664+
///
665+
/// **Latency note:** If a core is busy with other work (node, bot), SWQOS submit on that core can be delayed.
666+
/// For lowest latency, pass core indices that are *reserved* for SWQOS (do not run other CPU-heavy work on those cores).
639667
pub fn with_dedicated_sender_threads(mut self, core_indices: Option<Vec<usize>>) -> Self {
640668
match core_indices {
641669
None => {

src/trading/core/async_executor.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
//! - **Dedicated threads** (opt-in via `with_dedicated_sender_threads`): N OS threads run sender work only, optionally pinned to cores.
66
//! - **Arc**: Shared data behind `Arc` → clone = refcount increment (no data copy).
77
//! - **Refs**: `build_transaction` takes refs only; worker path avoids extra clones.
8+
//!
9+
//! **Core affinity & latency:** Each job is assigned a core (round-robin from `effective_core_ids`). When a worker runs a job,
10+
//! 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
11+
//! core will compete for CPU and latency can increase. For lowest latency, reserve a subset of cores for SWQOS only via
12+
//! `with_dedicated_sender_threads(Some(indices))` and avoid running other CPU-heavy work on those core indices.
813
914
use anyhow::{anyhow, Result};
1015
use crossbeam_queue::ArrayQueue;

0 commit comments

Comments
 (0)