Skip to content

bthread: support per-tag CPU affinity in --cpu_set#3331

Open
yanglimingcn wants to merge 1 commit into
apache:masterfrom
yanglimingcn:feature/cpu_set_support_multi_tag
Open

bthread: support per-tag CPU affinity in --cpu_set#3331
yanglimingcn wants to merge 1 commit into
apache:masterfrom
yanglimingcn:feature/cpu_set_support_multi_tag

Conversation

@yanglimingcn
Copy link
Copy Markdown
Contributor

Previously --cpu_set accepted a single CPU list (e.g. "0-3,5,7") that was applied uniformly to all bthread worker threads regardless of their bthread_tag.

This change extends the flag to accept a per-tag format:

--cpu_set="0:0-3,5,7;1:6-9,4"

where each semicolon-separated segment is ":". Tags not mentioned in the string get no CPU binding. The legacy single-list format continues to work unchanged and binds all tags to the same set.

Implementation:

  • Replace _cpus (vector) with _tag_cpus (vector<vector>), sized to FLAGS_task_group_ntags in the constructor.
  • Add parse_one_cpuset() (static helper) for the existing range-list parsing logic; parse_cpuset() now dispatches between legacy and per-tag formats based on the presence of ':' or ';'.
  • worker_thread() looks up _tag_cpus[tag] and round-robins over the per-tag CPU list using the global _next_worker_id counter.

What problem does this PR solve?

Issue Number: resolve

Problem Summary:

What is changed and the side effects?

Changed:

Side effects:

  • Performance effects:

  • Breaking backward compatibility:


Check List:

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the --cpu_set flag in bthread::TaskControl to support per-bthread_tag CPU affinity (while preserving the legacy single CPU-list format), enabling different worker-thread tags to be pinned to different CPU sets.

Changes:

  • Update --cpu_set flag documentation and parsing to accept tag:cpu-list segments separated by ;.
  • Replace the single shared CPU list with a per-tag CPU list (_tag_cpus).
  • Apply CPU binding per worker based on its tag’s CPU list.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
src/bthread/task_control.h Updates parse_cpuset API/doc and adds _tag_cpus per-tag CPU binding storage.
src/bthread/task_control.cpp Implements per-tag cpu_set parsing and applies per-tag affinity during worker thread startup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/bthread/task_control.cpp Outdated
Comment on lines +115 to +120
// worker_id is global and monotonically increasing; it doubles as the
// round-robin index for CPU affinity (same counter for name and binding).
int worker_id = c->_next_worker_id.fetch_add(1, butil::memory_order_relaxed);
if (!c->_tag_cpus[tag].empty()) {
const auto& cpus = c->_tag_cpus[tag];
bind_thread_to_cpu(pthread_self(), cpus[worker_id % cpus.size()]);
Comment thread src/bthread/task_control.cpp Outdated
Comment on lines +376 to +386
int TaskControl::parse_cpuset(const std::string& value,
std::vector<std::vector<unsigned>>& tag_cpus,
int ntags) {
if (value.empty()) {
return -1;
}
// Detect per-tag format by the presence of ':' or ';'.
// Legacy format ("0-3,5,7") never contains these characters.
bool per_tag_format = (value.find(';') != std::string::npos ||
value.find(':') != std::string::npos);

Comment on lines +402 to +409
std::string tag_str = segment.substr(0, colon);
std::string cpus_str = segment.substr(colon + 1);

unsigned tag_id = 0;
butil::StringPiece tag_sp(tag_str);
if (butil::StringSplitter(tag_sp, '\t').to_uint(&tag_id) != 0) {
LOG(ERROR) << "cpu_set invalid tag '" << tag_str << "'";
return -1;
Comment thread src/bthread/task_control.h Outdated
Comment on lines +100 to +106
// On success populates |tag_cpus| (indexed by tag) and returns 0.
// |tag_cpus| is resized to max_tag+1; tags that are not mentioned
// get an empty cpu list (= no binding).
// Returns -1 on parse error.
static int parse_cpuset(const std::string& value,
std::vector<std::vector<unsigned>>& tag_cpus,
int ntags);
Comment thread src/bthread/task_control.cpp Outdated
Comment on lines +376 to +386
int TaskControl::parse_cpuset(const std::string& value,
std::vector<std::vector<unsigned>>& tag_cpus,
int ntags) {
if (value.empty()) {
return -1;
}
// Detect per-tag format by the presence of ':' or ';'.
// Legacy format ("0-3,5,7") never contains these characters.
bool per_tag_format = (value.find(';') != std::string::npos ||
value.find(':') != std::string::npos);

@yanglimingcn yanglimingcn force-pushed the feature/cpu_set_support_multi_tag branch from e29075d to 6538752 Compare June 5, 2026 06:20
Previously --cpu_set accepted a single CPU list (e.g. "0-3,5,7") that
was applied uniformly to all bthread worker threads regardless of their
bthread_tag.

This change extends the flag to accept a per-tag format:

  --cpu_set="0:0-3,5,7;1:6-9,4"

where each semicolon-separated segment is "<tag>:<cpu-list>".  Tags not
mentioned in the string get no CPU binding.  The legacy single-list
format continues to work unchanged and binds all tags to the same set.

Implementation:
- Replace _cpus (vector<unsigned>) with _tag_cpus (vector<vector<unsigned>>),
  sized to FLAGS_task_group_ntags in the constructor.
- Add parse_one_cpuset() (static helper) for the existing range-list
  parsing logic; parse_cpuset() now dispatches between legacy and per-tag
  formats based on the presence of ':' or ';'.
- worker_thread() looks up _tag_cpus[tag] and round-robins over the
  per-tag CPU list using the global _next_worker_id counter.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

, _tagged_pl(FLAGS_task_group_ntags)
, _tag_cpus(FLAGS_task_group_ntags)
, _tag_next_worker_id(FLAGS_task_group_ntags)
{}
Comment on lines +368 to +370
for (auto i = first; i <= last; ++i) {
cpuset.insert(i);
}
Comment on lines +114 to +117
// tag_wid is a per-tag monotonic counter: same-tag workers get 0,1,2,...
// Used both for CPU round-robin affinity and the thread name suffix.
int tag_wid = c->_tag_next_worker_id[tag].fetch_add(
1, butil::memory_order_relaxed);
Copy link
Copy Markdown
Contributor

@chenBright chenBright left a comment

Choose a reason for hiding this comment

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

LGTM

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