Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
# x.async benchmarks
This folder contains cautious local benchmarks for the public `x.async` API.
They are observation tools, not correctness tests and not portable performance
claims.
## Available benchmark
- `async_benchmark.v`: measures short default runs for `Group`, `Task[T]`,
`Pool`, bounded pool admission, `with_timeout()`, and `every()`.
- `run_async_benchmark.sh`: builds and runs the benchmark with the local `./v`,
isolated `VTMP`/`VCACHE`, and a temporary output binary.
## Run
From the repository root:
```sh
sh vlib/x/async/benchmarks/run_async_benchmark.sh
```
The script is serialized by design. Do not run multiple V benchmark or test
runners against the same checkout/cache without isolating `VTMP`, `VCACHE`, and
output paths.
## Parameters
The defaults are intentionally small. Tune them locally with:
- `XASYNC_BENCH_GROUP_ROUNDS`
- `XASYNC_BENCH_GROUP_JOBS`
- `XASYNC_BENCH_TASK_ROUNDS`
- `XASYNC_BENCH_POOL_JOBS`
- `XASYNC_BENCH_POOL_WORKERS`
- `XASYNC_BENCH_POOL_BOUNDED_ROUNDS`
- `XASYNC_BENCH_POOL_BOUNDED_TIMEOUT_MS`
- `XASYNC_BENCH_TIMEOUT_ROUNDS`
- `XASYNC_BENCH_EVERY_ITERATIONS`
- `XASYNC_BENCH_EVERY_INTERVAL_MS`
The bounded pool admission case exercises `submit_with_timeout()` while the
pool is full and `submit_with_context()` after capacity opens. The timeout only
bounds admission; the benchmark does not make performance assertions.
Do not commit machine-specific benchmark results as permanent truth. Tests
remain the authority for functional and concurrency-safety validation.
@@ -0,0 +1,270 @@
import benchmark
import context
import os
import time
import x.async as xasync
const default_group_rounds = 8
const default_group_jobs = 16
const default_task_rounds = 32
const default_pool_jobs = 64
const default_pool_workers = 4
const default_pool_bounded_rounds = 8
const default_pool_bounded_timeout_ms = 2
const default_timeout_rounds = 32
const default_every_iterations = 5
const default_every_interval_ms = 1
fn main() {
run_benchmarks() or {
eprintln('x.async benchmark failed: ${err.msg()}')
exit(1)
}
}
fn run_benchmarks() ! {
group_rounds := env_int('XASYNC_BENCH_GROUP_ROUNDS', default_group_rounds, 1, 200)
group_jobs := env_int('XASYNC_BENCH_GROUP_JOBS', default_group_jobs, 1, 512)
task_rounds := env_int('XASYNC_BENCH_TASK_ROUNDS', default_task_rounds, 1, 500)
pool_jobs := env_int('XASYNC_BENCH_POOL_JOBS', default_pool_jobs, 1, 1000)
pool_workers := env_int('XASYNC_BENCH_POOL_WORKERS', default_pool_workers, 1, 64)
pool_bounded_rounds := env_int('XASYNC_BENCH_POOL_BOUNDED_ROUNDS', default_pool_bounded_rounds,
1, 100)
pool_bounded_timeout_ms := env_int('XASYNC_BENCH_POOL_BOUNDED_TIMEOUT_MS',
default_pool_bounded_timeout_ms, 1, 100)
timeout_rounds := env_int('XASYNC_BENCH_TIMEOUT_ROUNDS', default_timeout_rounds, 1, 500)
every_iterations := env_int('XASYNC_BENCH_EVERY_ITERATIONS', default_every_iterations, 1, 100)
every_interval_ms :=
env_int('XASYNC_BENCH_EVERY_INTERVAL_MS', default_every_interval_ms, 1, 100)
println('x.async cautious benchmark')
println('Override sizes with XASYNC_BENCH_* environment variables.')
mut checksum := 0
mut b := benchmark.start()
checksum += bench_group(group_rounds, group_jobs)!
b.measure('Group: rounds=${group_rounds}, jobs_per_round=${group_jobs}, checksum=${checksum}')
checksum += bench_task(task_rounds)!
b.measure('Task: rounds=${task_rounds}, checksum=${checksum}')
checksum += bench_pool(pool_jobs, pool_workers)!
b.measure('Pool: jobs=${pool_jobs}, workers=${pool_workers}, checksum=${checksum}')
checksum += bench_pool_bounded_admission(pool_bounded_rounds, pool_bounded_timeout_ms)!
b.measure('Pool bounded admission: rounds=${pool_bounded_rounds}, timeout_ms=${pool_bounded_timeout_ms}, checksum=${checksum}')
checksum += bench_timeout(timeout_rounds)!
b.measure('with_timeout: rounds=${timeout_rounds}, checksum=${checksum}')
checksum += bench_every(every_iterations, every_interval_ms)!
b.measure('every: iterations=${every_iterations}, interval_ms=${every_interval_ms}, checksum=${checksum}')
}
fn env_int(name string, default_value int, min_value int, max_value int) int {
raw := os.getenv(name)
if raw == '' {
return default_value
}
mut value := raw.int()
if value < min_value {
value = min_value
}
if value > max_value {
value = max_value
}
return value
}
fn bench_group(rounds int, jobs_per_round int) !int {
mut total := 0
for _ in 0 .. rounds {
done := chan int{cap: jobs_per_round}
mut group := xasync.new_group(context.background())
for _ in 0 .. jobs_per_round {
group.go(fn [done] (mut ctx context.Context) ! {
_ = ctx
done <- 1
})!
}
group.wait()!
for _ in 0 .. jobs_per_round {
total += <-done
}
}
return total
}
fn bench_task(rounds int) !int {
mut total := 0
for i in 0 .. rounds {
mut task := xasync.run[int](fn [i] (mut ctx context.Context) !int {
_ = ctx
return i + 1
})!
total += task.wait()!
}
return total
}
fn bench_pool(jobs int, workers int) !int {
done := chan int{cap: jobs}
mut pool := xasync.new_pool(workers: workers, queue_size: jobs)!
for _ in 0 .. jobs {
pool.try_submit(fn [done] (mut ctx context.Context) ! {
_ = ctx
done <- 1
})!
}
pool.close()!
mut total := 0
for _ in 0 .. jobs {
total += <-done
}
return total
}
fn bench_pool_bounded_admission(rounds int, timeout_ms int) !int {
mut total := 0
admission_timeout := time.Duration(timeout_ms) * time.millisecond
for _ in 0 .. rounds {
mut pool := xasync.new_pool(workers: 1, queue_size: 1)!
started := chan bool{cap: 2}
release := chan bool{cap: 3}
finished := chan bool{cap: 2}
attempting := chan bool{cap: 1}
accepted := chan bool{cap: 1}
ran := chan int{cap: 1}
blocking_job := fn [started, release, finished] (mut ctx context.Context) ! {
_ = ctx
started <- true
_ := <-release
finished <- true
}
pool.try_submit(blocking_job)!
wait_for_benchmark_bool(started, 'bounded admission pool job did not start')!
pool.try_submit(blocking_job)!
mut timed_out := false
pool.submit_with_timeout(admission_timeout, fn (mut ctx context.Context) ! {
_ = ctx
}) or {
if err.msg() != 'async: timeout' {
return err
}
timed_out = true
}
if !timed_out {
release <- true
release <- true
pool.close()!
return error('submit_with_timeout accepted while the pool was full')
}
submit_thread := spawn fn [mut pool, attempting, accepted, ran] () {
attempting <- true
pool.submit_with_context(context.background(), fn [ran] (mut ctx context.Context) ! {
_ = ctx
ran <- 1
}) or {
accepted <- false
return
}
accepted <- true
}()
wait_for_benchmark_bool(attempting, 'bounded admission submitter did not start')!
release <- true
wait_for_benchmark_bool(finished, 'bounded admission first job did not finish')!
wait_for_benchmark_bool(accepted,
'submit_with_context did not accept after capacity opened')!
release <- true
total += read_benchmark_int(ran, 'bounded admission accepted job did not run')!
pool.close()!
submit_thread.wait()
}
return total
}
fn bench_timeout(rounds int) !int {
mut total := 0
for _ in 0 .. rounds {
xasync.with_timeout(1 * time.second, fn (mut ctx context.Context) ! {
_ = ctx
})!
total++
}
return total
}
fn bench_every(iterations int, interval_ms int) !int {
ctx, cancel := xasync.with_cancel()
ticks := chan int{cap: iterations + 4}
result := chan string{cap: 1}
interval := time.Duration(interval_ms) * time.millisecond
worker := spawn fn [ctx, ticks, result, interval] () {
xasync.every(ctx, interval, fn [ticks] (mut ctx context.Context) ! {
_ = ctx
ticks <- 1
}) or {
result <- err.msg()
return
}
result <- 'ok'
}()
mut total := 0
for _ in 0 .. iterations {
select {
value := <-ticks {
total += value
}
1 * time.second {
cancel()
worker.wait()
return error('every benchmark did not receive a tick')
}
}
}
cancel()
select {
msg := <-result {
if msg != 'context canceled' {
worker.wait()
return error('unexpected every benchmark result: ${msg}')
}
}
1 * time.second {
worker.wait()
return error('every benchmark did not stop after cancellation')
}
}
worker.wait()
return total
}
fn wait_for_benchmark_bool(signal chan bool, message string) ! {
select {
ok := <-signal {
if !ok {
return error(message)
}
}
1 * time.second {
return error(message)
}
}
}
fn read_benchmark_int(signal chan int, message string) !int {
select {
value := <-signal {
return value
}
1 * time.second {
return error(message)
}
}
return 0
}
@@ -0,0 +1,32 @@
#!/usr/bin/env sh
set -eu
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
repo_root=$(CDPATH= cd "$script_dir/../../../.." && pwd)
cd "$repo_root"
if [ ! -x ./v ]; then
echo "x.async benchmarks must be run from a V checkout with local ./v" >&2
exit 1
fi
tmp_root=$(mktemp -d "${TMPDIR:-/tmp}/xasync-benchmark.XXXXXX")
cleanup() {
rm -rf "$tmp_root"
}
trap cleanup EXIT INT TERM
vtmp="$tmp_root/vtmp"
vcache="$tmp_root/vcache"
out="$tmp_root/out/async_benchmark"
mkdir -p "$vtmp" "$vcache" "$(dirname "$out")"
echo "Running x.async benchmark with isolated VTMP and VCACHE."
echo "Tune sizes with XASYNC_BENCH_* environment variables."
echo "See vlib/x/async/benchmarks/README.md for the full list."
if command -v timeout >/dev/null 2>&1; then
timeout 180s env VTMP="$vtmp" VCACHE="$vcache" ./v -prod -o "$out" run vlib/x/async/benchmarks/async_benchmark.v
else
env VTMP="$vtmp" VCACHE="$vcache" ./v -prod -o "$out" run vlib/x/async/benchmarks/async_benchmark.v
fi