Skip to content

feat(runtime): implement thread affinity for runtime and dispatcher #445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

numinnex
Copy link
Contributor

@numinnex numinnex commented Jul 3, 2025

This PR adds an extra field to both Runtime and Dispatcher builder that allows user to set thread affinity.

Copy link
Member

@Berrysoft Berrysoft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using core_affinity (or other equivalents) rather than nix, to support more platforms.

@George-Miao Do you think we need a feature gate for this PR?

@numinnex
Copy link
Contributor Author

numinnex commented Jul 3, 2025

I think given that core_affinity is an extra dependency, feature gate wouldn't hurt. I just used nix because I am not big of a fan of slapping extra deps.

Will take a look into core_affinity guarantees about other platforms support.

@numinnex
Copy link
Contributor Author

numinnex commented Jul 3, 2025

One more question tho, what behavior should we expose given an input that exceeds number of available CPUs ?

@numinnex numinnex force-pushed the runtime_dispatcher_thread_affinity branch from c90cb74 to ae48b26 Compare July 3, 2025 18:41
@numinnex
Copy link
Contributor Author

numinnex commented Jul 3, 2025

I have pushed changes that replace nix with core_affinity, but the question from above still stands.

@numinnex numinnex marked this pull request as draft July 4, 2025 04:39
@George-Miao
Copy link
Member

One more question tho, what behavior should we expose given an input that exceeds number of available CPUs ?

I think an error won't hurt.


thread_builder.spawn(move || {
Runtime::builder()
.with_proactor(proactor_builder)
.thread_affinity(cpus)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, you're binding all runtimes to same CPUs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed it to provide an callback (similar to thread name), so the user can decide on the affinity strategy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could provide a more sophisticated WorkerThread builder as the number of options starts to grow

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it OK to set default value to empty vec?

Copy link
Contributor Author

@numinnex numinnex Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I've added early exit inside of bind_to_cpu_set method in case of an empty collection.

@numinnex numinnex force-pushed the runtime_dispatcher_thread_affinity branch from 15a0b5e to 7530be6 Compare July 4, 2025 19:50
@numinnex numinnex force-pushed the runtime_dispatcher_thread_affinity branch from 06f1389 to 593fae3 Compare July 5, 2025 11:22
@@ -433,6 +441,7 @@ impl criterion::async_executor::AsyncExecutor for &Runtime {
#[derive(Debug, Clone)]
pub struct RuntimeBuilder {
proactor_builder: ProactorBuilder,
thread_affinity: Option<Vec<usize>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there difference between None and empty vec?

Copy link
Contributor Author

@numinnex numinnex Jul 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is none, so we technically could just treat empty collection as None variant.

@numinnex
Copy link
Contributor Author

numinnex commented Jul 5, 2025

I have changed the implementation from using Vec to use HashSet, the vec implementation was wrong and convoluted.

@numinnex numinnex force-pushed the runtime_dispatcher_thread_affinity branch from b201309 to 85f2aaf Compare July 5, 2025 12:17
Comment on lines +17 to +25
match (ids.iter().max(), cpus.iter().max()) {
(Some(max_id), Some(max_cpu)) if *max_cpu > *max_id => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("CPU ID: {max_cpu} exceeds maximum available CPU ID: {max_id}"),
));
}
_ => {}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, I am not sure whether such error is needed anymore tho... Since the set intersection will result in dropping the outliers, but at the same time, there will be no feedback to the end user what they are doing wrong...

@numinnex numinnex force-pushed the runtime_dispatcher_thread_affinity branch 2 times, most recently from d91ac7f to c3ea248 Compare July 5, 2025 13:46
@numinnex numinnex force-pushed the runtime_dispatcher_thread_affinity branch from c3ea248 to a390c77 Compare July 5, 2025 16:47
@numinnex
Copy link
Contributor Author

numinnex commented Jul 5, 2025

I don't understand why this test fails on MacOS and sadly I don't own one to test it locally :( but running those on my Linux setup works fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants