Skip to content

Commit 52b1ed1

Browse files
authored
Merge pull request #1364 from sporksmith/sched-options
Update scheduler options * The Experimental option `max-concurrency` is now the General option `parallelism`. * The default scheduler policy is now Host. * The General option `workers` is now the Experimental option `worker-threads`, and no longer has the `-w` shortcut. Users should use `parallelism` instead. If not set explicitly, the number of worker threads will be set to the number of hosts in the simulation. * "0" for "parallelism" is now rejected instead of being silently changed to 1.
2 parents c41f16b + 0793b98 commit 52b1ed1

8 files changed

Lines changed: 79 additions & 66 deletions

File tree

docs/3.1-Shadow-Config.md

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,15 @@ hosts:
2727
- [`general.data_directory`](#generaldata_directory)
2828
- [`general.heartbeat_interval`](#generalheartbeat_interval)
2929
- [`general.log_level`](#generallog_level)
30+
- [`general.parallelism`](#generalparallelism)
3031
- [`general.seed`](#generalseed)
3132
- [`general.stop_time`](#generalstop_time)
3233
- [`general.template_directory`](#generaltemplate_directory)
33-
- [`general.workers`](#generalworkers)
3434
- [`topology`](#topology)
3535
- [`experimental`](#experimental)
3636
- [`experimental.interface_buffer`](#experimentalinterface_buffer)
3737
- [`experimental.interface_qdisc`](#experimentalinterface_qdisc)
3838
- [`experimental.interpose_method`](#experimentalinterpose_method)
39-
- [`experimental.max_concurrency`](#experimentalmax_concurrency)
4039
- [`experimental.preload_spin_max`](#experimentalpreload_spin_max)
4140
- [`experimental.runahead`](#experimentalrunahead)
4241
- [`experimental.scheduler_policy`](#experimentalscheduler_policy)
@@ -52,6 +51,7 @@ hosts:
5251
- [`experimental.use_sched_fifo`](#experimentaluse_sched_fifo)
5352
- [`experimental.use_shim_syscall_handler`](#experimentaluse_shim_syscall_handler)
5453
- [`experimental.use_syscall_counters`](#experimentaluse_syscall_counters)
54+
- [`experimental.worker_threads`](#experimentalworker_threads)
5555
- [`host_defaults`](#host_defaults)
5656
- [`host_defaults.city_code_hint`](#host_defaultscity_code_hint)
5757
- [`host_defaults.country_code_hint`](#host_defaultscountry_code_hint)
@@ -106,6 +106,14 @@ Type: "error" OR "warning" OR "info" OR "debug" OR "trace"
106106

107107
Log level of output written on stdout. If Shadow was built in release mode, then messages at level 'trace' will always be dropped.
108108

109+
#### `general.parallelism`
110+
111+
Default: null
112+
Type: Integer OR null
113+
114+
How many parallel threads to use to run the simulation. Optimal performance is
115+
usually obtained with `nproc`, or sometimes `nproc/2` with hyperthreading.
116+
109117
#### `general.seed`
110118

111119
Default: 1
@@ -127,13 +135,6 @@ Type: String OR null
127135

128136
Path to recursively copy during startup and use as the data-directory.
129137

130-
#### `general.workers`
131-
132-
Default: 0
133-
Type: Integer
134-
135-
Run concurrently with N worker threads.
136-
137138
#### `topology`
138139

139140
*Required*
@@ -181,13 +182,6 @@ Type: "ptrace" OR "preload" OR "hybrid"
181182

182183
Which interposition method to use.
183184

184-
#### `experimental.max_concurrency`
185-
186-
Default: null
187-
Type: Integer OR null
188-
189-
Maximum number of workers to allow to run at once.
190-
191185
#### `experimental.preload_spin_max`
192186

193187
Default: 0
@@ -204,7 +198,7 @@ If set, overrides the automatically calculated minimum time workers may run ahea
204198

205199
#### `experimental.scheduler_policy`
206200

207-
Default: "steal"
201+
Default: "host"
208202
Type: "host" OR "steal" OR "thread" OR "threadxthread" OR "threadxhost"
209203

210204
The event scheduler's policy for thread synchronization.
@@ -293,6 +287,19 @@ Type: Bool
293287

294288
Count the number of occurrences for individual syscalls.
295289

290+
#### `experimental.worker_threads`
291+
292+
Default: # of hosts in the simulation
293+
Type: Integer
294+
295+
Create N worker threads. Note though, that `general.parallelism` of them will be
296+
allowed to run simultaneously. If unset, will create a thread for each simulated
297+
Host. This is to work around limitations in ptrace, and may change in the
298+
future.
299+
300+
"0" is a valid value, and will cause the simulation to be run directly on the
301+
main Shadow thread, but this functionality may be removed in the future.
302+
296303
#### `host_defaults`
297304

298305
Default options for all hosts. These options can also be overridden for each host individually.

src/main/bindings/c/bindings.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ bool config_getUseShimSyscallHandler(const struct ConfigOptions *config);
118118

119119
int32_t config_getPreloadSpinMax(const struct ConfigOptions *config);
120120

121-
int32_t config_getMaxConcurrency(const struct ConfigOptions *config);
121+
uint32_t config_getParallelism(const struct ConfigOptions *config);
122122

123123
SimulationTime config_getStopTime(const struct ConfigOptions *config);
124124

src/main/core/scheduler/scheduler.c

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
#include "main/utility/utility.h"
2727
#include "support/logger/logger.h"
2828

29-
static int _maxConcurrency = -1;
30-
ADD_CONFIG_HANDLER(config_getMaxConcurrency, _maxConcurrency)
29+
static int _parallelism;
30+
ADD_CONFIG_HANDLER(config_getParallelism, _parallelism)
3131

3232
struct _Scheduler {
3333
// Unowned back-pointer.
@@ -130,9 +130,8 @@ Scheduler* scheduler_new(Manager* manager, SchedulerPolicyType policyType,
130130
// Unowned back-pointer
131131
scheduler->manager = manager;
132132

133-
scheduler->workerPool =
134-
workerpool_new(manager, scheduler, /*nThreads=*/nWorkers,
135-
/*nConcurrent=*/_maxConcurrency);
133+
scheduler->workerPool = workerpool_new(manager, scheduler, /*nThreads=*/nWorkers,
134+
/*nParallel=*/_parallelism);
136135

137136
scheduler->endTime = endTime;
138137
scheduler->currentRound.endTime = scheduler->endTime;// default to one single round
@@ -160,12 +159,12 @@ Scheduler* scheduler_new(Manager* manager, SchedulerPolicyType policyType,
160159
break;
161160
}
162161
case SP_PARALLEL_HOST_STEAL: {
163-
if (nWorkers > _maxConcurrency) {
162+
if (nWorkers > _parallelism) {
164163
// Proceeding will cause the scheduler to deadlock, since the
165164
// work stealing scheduler threads spin-wait for each-other to
166165
// finish.
167166
utility_panic("Host stealing scheduler is incompatible with "
168-
"--workers > --max-concurrency");
167+
"--workers > --parallelism");
169168
abort();
170169
}
171170
scheduler->policy = schedulerpolicyhoststeal_new();

src/main/core/support/configuration.rs

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ use merge::Merge;
88
use once_cell::sync::Lazy;
99
use schemars::{schema_for, JsonSchema};
1010
use serde::{Deserialize, Serialize};
11+
use std::convert::TryInto;
12+
use std::num::NonZeroU32;
1113

1214
use super::simulation_time::{SIMTIME_ONE_NANOSECOND, SIMTIME_ONE_SECOND};
1315
use super::units::{self, Unit};
@@ -133,11 +135,13 @@ pub struct GeneralOptions {
133135
#[serde(default = "default_some_1")]
134136
seed: Option<u32>,
135137

136-
/// Run concurrently with N worker threads
137-
#[clap(long, short = 'w', value_name = "N")]
138-
#[clap(about = GENERAL_HELP.get("workers").unwrap())]
139-
#[serde(default = "default_some_0")]
140-
workers: Option<u32>,
138+
/// How many parallel threads to use to run the simulation. Optimal
139+
/// performance is usually obtained with `nproc`, or sometimes `nproc/2`
140+
/// with hyperthreading.
141+
#[clap(long, short = 'p', value_name = "cores")]
142+
#[clap(about = GENERAL_HELP.get("parallelism").unwrap())]
143+
#[serde(default = "default_some_nz_1")]
144+
parallelism: Option<NonZeroU32>,
141145

142146
#[clap(long, value_name = "seconds")]
143147
#[clap(about = GENERAL_HELP.get("bootstrap_end_time").unwrap())]
@@ -221,11 +225,6 @@ pub struct ExperimentalOptions {
221225
#[clap(about = EXP_HELP.get("preload_spin_max").unwrap())]
222226
preload_spin_max: Option<i32>,
223227

224-
/// Maximum number of workers to allow to run at once
225-
#[clap(long, value_name = "workers")]
226-
#[clap(about = EXP_HELP.get("max_concurrency").unwrap())]
227-
max_concurrency: Option<i32>,
228-
229228
/// Use the MemoryManager. It can be useful to disable for debugging, but will hurt performance in
230229
/// most cases
231230
#[clap(long, value_name = "bool")]
@@ -286,6 +285,16 @@ pub struct ExperimentalOptions {
286285
#[clap(long, value_name = "mode")]
287286
#[clap(about = EXP_HELP.get("interface_qdisc").unwrap())]
288287
interface_qdisc: Option<QDiscMode>,
288+
289+
/// Create N worker threads. Note though, that `--parallelism` of them will
290+
/// be allowed to run simultaneously. If unset, will create a thread for
291+
/// each simulated Host. This is to work around limitations in ptrace, and
292+
/// may change in the future. "0" is a valid value, and will cause the
293+
/// simulation to be run directly on the main Shadow thread, but this
294+
/// functionality may be removed in the future.
295+
#[clap(long, value_name = "N")]
296+
#[clap(about = EXP_HELP.get("worker_threads").unwrap())]
297+
worker_threads: Option<u32>,
289298
}
290299

291300
impl ExperimentalOptions {
@@ -305,19 +314,19 @@ impl Default for ExperimentalOptions {
305314
use_syscall_counters: Some(false),
306315
use_object_counters: Some(true),
307316
preload_spin_max: Some(0),
308-
max_concurrency: None,
309317
use_memory_manager: Some(true),
310318
use_shim_syscall_handler: Some(true),
311319
use_cpu_pinning: Some(true),
312320
interpose_method: Some(InterposeMethod::Ptrace),
313321
runahead: None,
314-
scheduler_policy: Some(SchedulerPolicy::Steal),
322+
scheduler_policy: Some(SchedulerPolicy::Host),
315323
socket_send_buffer: Some(units::Bytes::new(131_072, units::SiPrefixUpper::Base)),
316324
socket_send_autotune: Some(true),
317325
socket_recv_buffer: Some(units::Bytes::new(174_760, units::SiPrefixUpper::Base)),
318326
socket_recv_autotune: Some(true),
319327
interface_buffer: Some(units::Bytes::new(1_024_000, units::SiPrefixUpper::Base)),
320328
interface_qdisc: Some(QDiscMode::Fifo),
329+
worker_threads: None,
321330
}
322331
}
323332
}
@@ -640,16 +649,16 @@ fn default_some_time_0() -> Option<units::Time<units::TimePrefixUpper>> {
640649
Some(units::Time::new(0, units::TimePrefixUpper::Sec))
641650
}
642651

643-
/// Helper function for serde default `Some(0)` values.
644-
fn default_some_0() -> Option<u32> {
645-
Some(0)
646-
}
647-
648652
/// Helper function for serde default `Some(1)` values.
649653
fn default_some_1() -> Option<u32> {
650654
Some(1)
651655
}
652656

657+
/// Helper function for serde default `Some(1)` values.
658+
fn default_some_nz_1() -> Option<NonZeroU32> {
659+
Some(std::num::NonZeroU32::new(1).unwrap())
660+
}
661+
653662
/// Helper function for serde default `Some(0)` values.
654663
fn default_some_time_1() -> Option<units::Time<units::TimePrefixUpper>> {
655664
Some(units::Time::new(1, units::TimePrefixUpper::Sec))
@@ -1106,13 +1115,10 @@ mod export {
11061115
}
11071116

11081117
#[no_mangle]
1109-
pub extern "C" fn config_getMaxConcurrency(config: *const ConfigOptions) -> i32 {
1118+
pub extern "C" fn config_getParallelism(config: *const ConfigOptions) -> NonZeroU32 {
11101119
assert!(!config.is_null());
11111120
let config = unsafe { &*config };
1112-
match config.experimental.max_concurrency {
1113-
Some(x) => x,
1114-
None => -1,
1115-
}
1121+
config.general.parallelism.unwrap()
11161122
}
11171123

11181124
#[no_mangle]
@@ -1149,7 +1155,13 @@ mod export {
11491155
pub extern "C" fn config_getWorkers(config: *const ConfigOptions) -> libc::c_uint {
11501156
assert!(!config.is_null());
11511157
let config = unsafe { &*config };
1152-
config.general.workers.unwrap()
1158+
match &config.experimental.worker_threads {
1159+
Some(w) => *w,
1160+
None => {
1161+
// By default use 1 worker per host.
1162+
config.hosts.len().try_into().unwrap()
1163+
}
1164+
}
11531165
}
11541166

11551167
#[no_mangle]

src/main/core/worker.c

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,12 @@ static Worker* _worker_new(WorkerPool*, int);
134134
static void _worker_free(Worker*);
135135

136136
WorkerPool* workerpool_new(Manager* manager, Scheduler* scheduler, int nWorkers,
137-
int nConcurrent) {
138-
int nLogicalProcessors = 0;
139-
if (nWorkers == 0 || nConcurrent == 0) {
140-
// With no concurrency, we still use a single logical processor.
141-
nLogicalProcessors = 1;
142-
} else if (nConcurrent < 0 || nConcurrent > nWorkers) {
143-
// Never makes sense to use more logical processors than workers.
144-
nLogicalProcessors = nWorkers;
145-
} else {
146-
nLogicalProcessors = nConcurrent;
147-
}
137+
int nParallel) {
138+
// Should have been ensured earlier by `config_getParallelism`.
139+
utility_assert(nParallel >= 1);
140+
141+
// Never makes sense to use more logical processors than workers.
142+
int nLogicalProcessors = MIN(nParallel, nWorkers);
148143

149144
WorkerPool* pool = g_new(WorkerPool, 1);
150145
*pool = (WorkerPool){

src/test/determinism/CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ foreach(METHOD ptrace hybrid)
1111
BASENAME determinism1a
1212
METHODS ${METHOD}
1313
SHADOW_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/determinism1.test.shadow.config.yaml
14-
ARGS --use-cpu-pinning true --workers 2
14+
ARGS --use-cpu-pinning true --parallelism 2
1515
PROPERTIES RUN_SERIAL TRUE)
1616
add_shadow_tests(
1717
BASENAME determinism1b
1818
METHODS ${METHOD}
1919
SHADOW_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/determinism1.test.shadow.config.yaml
20-
ARGS --use-cpu-pinning true --workers 2
20+
ARGS --use-cpu-pinning true --parallelism 2
2121
PROPERTIES RUN_SERIAL TRUE)
2222
add_test(
2323
NAME determinism1-compare-shadow-${METHOD}
@@ -39,13 +39,13 @@ foreach(METHOD ptrace hybrid)
3939
BASENAME determinism2a
4040
METHODS ${METHOD}
4141
SHADOW_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/determinism2.test.shadow.config.yaml
42-
ARGS --use-cpu-pinning true --workers 2
42+
ARGS --use-cpu-pinning true --parallelism 2
4343
PROPERTIES RUN_SERIAL TRUE)
4444
add_shadow_tests(
4545
BASENAME determinism2b
4646
METHODS ${METHOD}
4747
SHADOW_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/determinism2.test.shadow.config.yaml
48-
ARGS --use-cpu-pinning true --workers 2
48+
ARGS --use-cpu-pinning true --parallelism 2
4949
PROPERTIES RUN_SERIAL TRUE)
5050
## now compare the output
5151
## TODO enable this test and fix the remaining determinism issue

src/test/phold/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ add_shadow_tests(
2727
BASENAME phold-parallel
2828
METHODS hybrid ptrace
2929
LOGLEVEL info
30-
ARGS --use-cpu-pinning true --workers 2
30+
ARGS --use-cpu-pinning true --parallelism 2
3131
PROPERTIES RUN_SERIAL TRUE)
3232
add_shadow_tests(
3333
BASENAME phold-parallel
3434
METHODS preload
3535
LOGLEVEL info
36-
ARGS --use-cpu-pinning true --workers 2
36+
ARGS --use-cpu-pinning true --parallelism 2
3737
PROPERTIES RUN_SERIAL TRUE
3838
CONFIGURATIONS ilibc)
3939

src/test/tor/minimal/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ add_shadow_tests(BASENAME tor-minimal
2525
LOGLEVEL info
2626
ARGS
2727
--use-cpu-pinning true
28-
--workers 2
28+
--parallelism 2
2929
--template-directory "shadow.data.template"
3030
POST_CMD "${CMAKE_CURRENT_SOURCE_DIR}/verify.sh"
3131
PROPERTIES

0 commit comments

Comments
 (0)