Skip to content

Commit ecfb6bc

Browse files
authored
Merge pull request #2448 from subspace/exact-plotting-cpu-cores
Allow specifying exact plotting/replotting CPU cores
2 parents cbd11c2 + 34dfbec commit ecfb6bc

File tree

2 files changed

+79
-15
lines changed

2 files changed

+79
-15
lines changed

crates/subspace-farmer/src/bin/subspace-farmer/commands/farm.rs

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ use subspace_farmer::utils::piece_validator::SegmentCommitmentPieceValidator;
3030
use subspace_farmer::utils::readers_and_pieces::ReadersAndPieces;
3131
use subspace_farmer::utils::ss58::parse_ss58_reward_address;
3232
use subspace_farmer::utils::{
33-
all_cpu_cores, create_plotting_thread_pool_manager, run_future_in_dedicated_thread,
34-
thread_pool_core_indices, AsyncJoinOnDrop,
33+
all_cpu_cores, create_plotting_thread_pool_manager, parse_cpu_cores_sets,
34+
run_future_in_dedicated_thread, thread_pool_core_indices, AsyncJoinOnDrop,
3535
};
3636
use subspace_farmer::{Identity, NodeClient, NodeRpcClient};
3737
use subspace_farmer_components::plotting::PlottedSector;
@@ -138,6 +138,17 @@ pub(crate) struct FarmingArgs {
138138
/// Threads will be pinned to corresponding CPU cores at creation.
139139
#[arg(long)]
140140
plotting_thread_pool_size: Option<NonZeroUsize>,
141+
/// Specify exact CPU cores to be used for plotting bypassing any custom logic farmer might use
142+
/// otherwise. It replaces both `--sector-encoding-concurrency` and
143+
/// `--plotting-thread-pool-size` options if specified. Requires `--replotting-cpu-cores` to be
144+
/// specified with the same number of CPU cores groups (or not specified at all, in which case
145+
/// it'll use the same thread pool as plotting).
146+
///
147+
/// Cores are coma-separated, with whitespace separating different thread pools/encoding
148+
/// instances. For example "0,1 2,3" will result in two sectors being encoded at the same time,
149+
/// each with a pair of CPU cores.
150+
#[arg(long, conflicts_with_all = &["sector_encoding_concurrency", "plotting_thread_pool_size"])]
151+
plotting_cpu_cores: Option<String>,
141152
/// Size of one thread pool used for replotting, typically smaller pool than for plotting
142153
/// to not affect farming as much, defaults to half of the number of logical CPUs available on
143154
/// UMA system and number of logical CPUs available in NUMA node on NUMA system.
@@ -148,6 +159,15 @@ pub(crate) struct FarmingArgs {
148159
/// Threads will be pinned to corresponding CPU cores at creation.
149160
#[arg(long)]
150161
replotting_thread_pool_size: Option<NonZeroUsize>,
162+
/// Specify exact CPU cores to be used for replotting bypassing any custom logic farmer might
163+
/// use otherwise. It replaces `--replotting-thread_pool_size` options if specified. Requires
164+
/// `--plotting-cpu-cores` to be specified with the same number of CPU cores groups.
165+
///
166+
/// Cores are coma-separated, with whitespace separating different thread pools/encoding
167+
/// instances. For example "0,1 2,3" will result in two sectors being encoded at the same time,
168+
/// each with a pair of CPU cores.
169+
#[arg(long, conflicts_with_all = &["sector_encoding_concurrency", "replotting_thread_pool_size"])]
170+
replotting_cpu_cores: Option<String>,
151171
}
152172

153173
fn cache_percentage_parser(s: &str) -> anyhow::Result<NonZeroU8> {
@@ -286,7 +306,9 @@ where
286306
farm_during_initial_plotting,
287307
farming_thread_pool_size,
288308
plotting_thread_pool_size,
309+
plotting_cpu_cores,
289310
replotting_thread_pool_size,
311+
replotting_cpu_cores,
290312
} = farming_args;
291313

292314
// Override flags with `--dev`
@@ -431,19 +453,41 @@ where
431453
None => farmer_app_info.protocol_info.max_pieces_in_sector,
432454
};
433455

434-
let plotting_thread_pool_core_indices =
435-
thread_pool_core_indices(plotting_thread_pool_size, sector_encoding_concurrency);
436-
let replotting_thread_pool_core_indices = {
437-
let mut replotting_thread_pool_core_indices =
438-
thread_pool_core_indices(replotting_thread_pool_size, sector_encoding_concurrency);
439-
if replotting_thread_pool_size.is_none() {
440-
// The default behavior is to use all CPU cores, but for replotting we just want half
441-
replotting_thread_pool_core_indices
442-
.iter_mut()
443-
.for_each(|set| set.truncate(set.cpu_cores().len() / 2));
456+
let plotting_thread_pool_core_indices;
457+
let replotting_thread_pool_core_indices;
458+
if let Some(plotting_cpu_cores) = plotting_cpu_cores {
459+
plotting_thread_pool_core_indices = parse_cpu_cores_sets(&plotting_cpu_cores)
460+
.map_err(|error| anyhow::anyhow!("Failed to parse `--plotting-cpu-cores`: {error}"))?;
461+
replotting_thread_pool_core_indices = match replotting_cpu_cores {
462+
Some(replotting_cpu_cores) => {
463+
parse_cpu_cores_sets(&replotting_cpu_cores).map_err(|error| {
464+
anyhow::anyhow!("Failed to parse `--replotting-cpu-cores`: {error}")
465+
})?
466+
}
467+
None => plotting_thread_pool_core_indices.clone(),
468+
};
469+
if plotting_thread_pool_core_indices.len() != replotting_thread_pool_core_indices.len() {
470+
return Err(anyhow::anyhow!(
471+
"Number of plotting thread pools ({}) is not the same as for replotting ({})",
472+
plotting_thread_pool_core_indices.len(),
473+
replotting_thread_pool_core_indices.len()
474+
));
444475
}
445-
replotting_thread_pool_core_indices
446-
};
476+
} else {
477+
plotting_thread_pool_core_indices =
478+
thread_pool_core_indices(plotting_thread_pool_size, sector_encoding_concurrency);
479+
replotting_thread_pool_core_indices = {
480+
let mut replotting_thread_pool_core_indices =
481+
thread_pool_core_indices(replotting_thread_pool_size, sector_encoding_concurrency);
482+
if replotting_thread_pool_size.is_none() {
483+
// The default behavior is to use all CPU cores, but for replotting we just want half
484+
replotting_thread_pool_core_indices
485+
.iter_mut()
486+
.for_each(|set| set.truncate(set.cpu_cores().len() / 2));
487+
}
488+
replotting_thread_pool_core_indices
489+
};
490+
}
447491

448492
let downloading_semaphore = Arc::new(Semaphore::new(
449493
sector_downloading_concurrency

crates/subspace-farmer/src/utils.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ use futures::channel::oneshot::Canceled;
1111
use futures::future::Either;
1212
use rayon::{ThreadBuilder, ThreadPool, ThreadPoolBuildError, ThreadPoolBuilder};
1313
use std::future::Future;
14-
use std::num::NonZeroUsize;
14+
use std::num::{NonZeroUsize, ParseIntError};
1515
use std::ops::Deref;
1616
use std::pin::{pin, Pin};
17+
use std::str::FromStr;
1718
use std::task::{Context, Poll};
1819
use std::{io, thread};
1920
use tokio::runtime::Handle;
@@ -223,6 +224,25 @@ pub fn all_cpu_cores() -> Vec<CpuCoreSet> {
223224
}]
224225
}
225226

227+
/// Parse space-separated set of groups of CPU cores (individual cores are coma-separated) into
228+
/// vector of CPU core sets that can be used for creation of plotting/replotting thread pools.
229+
pub fn parse_cpu_cores_sets(s: &str) -> Result<Vec<CpuCoreSet>, ParseIntError> {
230+
s.split(' ')
231+
.map(|s| {
232+
let cores = s
233+
.split(',')
234+
.map(usize::from_str)
235+
.collect::<Result<Vec<usize>, _>>()?;
236+
237+
Ok(CpuCoreSet {
238+
cores,
239+
#[cfg(feature = "numa")]
240+
topology: hwlocality::Topology::new().map(std::sync::Arc::new).ok(),
241+
})
242+
})
243+
.collect()
244+
}
245+
226246
/// Thread indices for each thread pool
227247
pub fn thread_pool_core_indices(
228248
thread_pool_size: Option<NonZeroUsize>,

0 commit comments

Comments
 (0)