Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
提交
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 82 additions & 190 deletions .agent/HANDOFF.md

Large diffs are not rendered by default.

12 changes: 3 additions & 9 deletions .agents/skills/mcpp-style-ref/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,9 @@ mcpp --version

## 当前项目结构

参考本仓库 `src/` 目录结构:

- `.xlings.json`:声明项目工具环境
- `mcpp.toml`:声明 `[package]` 与测试依赖;简单库目标可由 mcpp 从 `src/*.cppm` 自动推断
- `src/cmp.cppm`:库主模块接口,导出 `:task` 与 `:run_loop` 分区
- `src/task.cppm`:`Task<T>` 与 `Task<void>` 分区
- `src/run_loop.cppm`:`RunLoop` 与 `Scheduler` 分区
- `tests/cmp_test.cpp`、`tests/run_loop_test.cpp`:`mcpp test` 自动发现的 gtest 测试;不要定义 `main()`
- `examples/basic/`:独立 mcpp consumer 包,通过 path 依赖引用根库
开始审查 CMP 前先读取[架构文档](../../../docs/architecture.zh.md),以其中的公共边界和目录结构
为当前事实来源。模块根接口位于 `src/cmp.cppm`,`tests/**/*.cpp` 由 `mcpp test` 自动发现,
`examples/basic/` 是独立的 path-dependency consumer;不要在技能内重复维护完整分区清单。

构建:

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ concurrency:

env:
# Pinned rather than "newest": a bootstrap that floats turns an upstream
# release into a red build on an unrelated PR. Floor is xlings 0.4.69
# (the index keys by (namespace, name) from there on) — never pin below it.
XLINGS_VERSION: v2026.8.11.2
# release into a red build on an unrelated PR. 2026.8.27.5 is the cold-cache
# floor: older clients can declare glibc 2.44 while installing 2.44.2.
XLINGS_VERSION: v2026.8.27.5
XLINGS_NON_INTERACTIVE: '1'

jobs:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ concurrency:
cancel-in-progress: true

env:
XLINGS_VERSION: v2026.8.11.2
# Keep aligned with the Linux cold-cache floor.
XLINGS_VERSION: v2026.8.27.5
XLINGS_NON_INTERACTIVE: '1'

jobs:
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/ci-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ concurrency:

env:
# Both installers read this: the shell one takes it as an argument or an env
# var, the PowerShell one defaults its -Version parameter to it.
XLINGS_VERSION: v2026.8.11.2
# var, the PowerShell one defaults its -Version parameter to it. Keep this
# aligned with the Linux cold-cache floor.
XLINGS_VERSION: v2026.8.27.5
XLINGS_NON_INTERACTIVE: '1'

jobs:
Expand Down
2 changes: 1 addition & 1 deletion .xlings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"workspace": {
"mcpp": "2026.8.11.2"
"mcpp": "2026.8.28.1"
}
}
187 changes: 154 additions & 33 deletions README.md

Large diffs are not rendered by default.

169 changes: 141 additions & 28 deletions README.zh.hant.md

Large diffs are not rendered by default.

169 changes: 141 additions & 28 deletions README.zh.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions benchmarks/thread-pool/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "cmp-thread-pool-benchmark"
version = "0.1.0"
standard = "c++23"
description = "Standalone CMP thread-pool scheduling benchmark"
license = "Apache-2.0"

[dependencies.mcpplibs]
cmp = { path = "../.." }
291 changes: 291 additions & 0 deletions benchmarks/thread-pool/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import std;
import mcpplibs.cmp;

namespace {

using mcpplibs::cmp::RunLoop;
using mcpplibs::cmp::Task;
using mcpplibs::cmp::TaskGroup;
using mcpplibs::cmp::ThreadPool;
using mcpplibs::cmp::when_all;

using Scheduler = ThreadPool::Scheduler;
using Clock = std::chrono::steady_clock;

constexpr int CPU_TASK_COUNT { 512 };
constexpr int CPU_STEPS { 200'000 };
constexpr int SCHEDULE_HOPS { 200'000 };
constexpr int NESTED_TASKS { 20'000 };
constexpr int PRODUCER_COUNT { 8 };
constexpr int HOPS_PER_PRODUCER { 25'000 };

struct Counters final {
std::atomic<std::size_t> successes_ {};
std::atomic<std::size_t> unexpectedFailures_ {};
std::atomic<std::size_t> active_ {};
std::atomic<std::size_t> maxConcurrency_ {};
};

struct Metrics final {
std::string scenario_ {};
std::size_t workerCount_ {};
std::size_t operations_ {};
std::size_t successes_ {};
std::size_t unexpectedFailures_ {};
std::size_t maxConcurrency_ {};
double elapsedMilliseconds_ {};

[[nodiscard]] bool passed() const noexcept {
return successes_ == operations_ && unexpectedFailures_ == 0;
}
};

void record_completion(Counters& counters) noexcept {
const auto active = counters.active_.fetch_add(
1,
std::memory_order_relaxed) + 1;
auto maximum = counters.maxConcurrency_.load(std::memory_order_relaxed);

while (maximum < active &&
!counters.maxConcurrency_.compare_exchange_weak(
maximum,
active,
std::memory_order_relaxed)) {
}

counters.successes_.fetch_add(1, std::memory_order_relaxed);
counters.active_.fetch_sub(1, std::memory_order_relaxed);
}

[[nodiscard]] std::uint64_t compute_value(int index) noexcept {
std::uint64_t value = static_cast<std::uint64_t>(index) + 1;

for (int step { 0 }; step < CPU_STEPS; ++step) {
value ^= value << 13;
value ^= value >> 7;
value ^= value << 17;
}

return value;
}

Task<void> compute_operation(
Scheduler scheduler,
int index,
Counters& counters,
std::atomic<std::uint64_t>& checksum) {
co_await scheduler.schedule();

const auto active = counters.active_.fetch_add(
1,
std::memory_order_relaxed) + 1;
auto maximum = counters.maxConcurrency_.load(std::memory_order_relaxed);
while (maximum < active &&
!counters.maxConcurrency_.compare_exchange_weak(
maximum,
active,
std::memory_order_relaxed)) {
}

checksum.fetch_xor(compute_value(index), std::memory_order_relaxed);
counters.successes_.fetch_add(1, std::memory_order_relaxed);
counters.active_.fetch_sub(1, std::memory_order_relaxed);
}

Task<void> run_compute(
Scheduler scheduler,
Counters& counters,
std::uint64_t expectedChecksum) {
std::atomic<std::uint64_t> checksum {};
TaskGroup group {};

for (int index { 0 }; index < CPU_TASK_COUNT; ++index) {
group.spawn(compute_operation(
scheduler,
index,
counters,
checksum));
}

co_await group.join();
if (checksum.load(std::memory_order_relaxed) != expectedChecksum) {
counters.unexpectedFailures_.fetch_add(1);
}
}

Task<void> run_schedule_hops(
Scheduler scheduler,
Counters& counters) {
for (int index { 0 }; index < SCHEDULE_HOPS; ++index) {
co_await scheduler.schedule();
record_completion(counters);
}
}

Task<void> run_one_shot(
Scheduler scheduler,
Counters& counters) {
co_await scheduler.schedule();
record_completion(counters);
}

Task<void> run_nested_fanout(
Scheduler scheduler,
Counters& counters) {
co_await scheduler.schedule();
std::vector<Task<void>> tasks {};
tasks.reserve(NESTED_TASKS);

for (int index { 0 }; index < NESTED_TASKS; ++index) {
tasks.emplace_back(run_one_shot(scheduler, counters));
}

static_cast<void>(co_await when_all(std::move(tasks)));
}

Task<void> run_producer(
Scheduler scheduler,
Counters& counters) {
for (int index { 0 }; index < HOPS_PER_PRODUCER; ++index) {
co_await scheduler.schedule();
record_completion(counters);
}
}

Task<void> run_concurrent_producers(
Scheduler scheduler,
Counters& counters) {
TaskGroup group {};

for (int index { 0 }; index < PRODUCER_COUNT; ++index) {
group.spawn(run_producer(scheduler, counters));
}

co_await group.join();
}

template<typename Scenario>
[[nodiscard]] Metrics measure(
std::string scenario,
ThreadPool& pool,
std::size_t operations,
Scenario scenarioTask) {
Counters counters {};
RunLoop loop {};
const auto start = Clock::now();

try {
loop.run(scenarioTask(pool.get_scheduler(), counters));
} catch (...) {
counters.unexpectedFailures_.fetch_add(1);
}

const auto elapsed = Clock::now() - start;
return Metrics {
std::move(scenario),
pool.thread_count(),
operations,
counters.successes_.load(),
counters.unexpectedFailures_.load(),
counters.maxConcurrency_.load(),
std::chrono::duration<double, std::milli> { elapsed }.count()
};
}

void print_metrics(const Metrics& metrics) {
const double throughput = metrics.elapsedMilliseconds_ > 0.0
? static_cast<double>(metrics.operations_) * 1'000.0 /
metrics.elapsedMilliseconds_
: 0.0;

std::println(
"{},{},{},{},{},{},{:.3f},{:.1f},{}",
metrics.scenario_,
metrics.workerCount_,
metrics.operations_,
metrics.successes_,
metrics.unexpectedFailures_,
metrics.maxConcurrency_,
metrics.elapsedMilliseconds_,
throughput,
metrics.passed() ? "PASS" : "FAIL");
}

[[nodiscard]] std::vector<std::size_t> worker_counts() {
const auto hardware = std::max<std::size_t>(
1,
std::thread::hardware_concurrency());
const auto maximum = std::min<std::size_t>(hardware, 8);
std::vector<std::size_t> counts {};

for (const std::size_t candidate : { 1U, 2U, 4U, 8U }) {
if (candidate <= maximum) {
counts.push_back(candidate);
}
}

if (counts.back() != maximum) {
counts.push_back(maximum);
}

return counts;
}

[[nodiscard]] std::uint64_t expected_checksum() noexcept {
std::uint64_t checksum {};

for (int index { 0 }; index < CPU_TASK_COUNT; ++index) {
checksum ^= compute_value(index);
}

return checksum;
}

} // namespace

int main() {
const auto expectedChecksum = expected_checksum();
std::vector<Metrics> results {};

for (const auto workerCount : worker_counts()) {
ThreadPool pool { workerCount };

results.emplace_back(measure(
"cpu_chunks",
pool,
CPU_TASK_COUNT,
[&](Scheduler scheduler, Counters& counters) {
return run_compute(
scheduler,
counters,
expectedChecksum);
}));
results.emplace_back(measure(
"schedule_hops",
pool,
SCHEDULE_HOPS,
run_schedule_hops));
results.emplace_back(measure(
"nested_fanout",
pool,
NESTED_TASKS,
run_nested_fanout));
results.emplace_back(measure(
"concurrent_reschedule",
pool,
PRODUCER_COUNT * HOPS_PER_PRODUCER,
run_concurrent_producers));
}

std::println(
"environment,hardware_threads={}",
std::thread::hardware_concurrency());
std::println(
"scenario,workers,operations,successes,unexpected_failures,max_concurrency,elapsed_ms,ops_per_second,status");

for (const auto& result : results) {
print_metrics(result);
}

return std::ranges::all_of(results, &Metrics::passed) ? 0 : 1;
}
9 changes: 9 additions & 0 deletions benchmarks/v1-readiness/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "cmp-v1-readiness"
version = "0.1.0"
standard = "c++23"
description = "Standalone CMP v1 workload readiness benchmark"
license = "Apache-2.0"

[dependencies.mcpplibs]
cmp = { path = "../.." }
Loading
Loading