From: Changbin Du <changbin.du@gmail.com>
To: Namhyung Kim <namhyung@kernel.org>
Cc: Changbin Du <changbin.du@gmail.com>,
Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Mark Rutland <mark.rutland@arm.com>,
Alexander Shishkin <alexander.shishkin@linux.intel.com>,
Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org,
David Laight <david.laight.linux@gmail.com>
Subject: Re: [PATCH v4] perf bench: Add atomic operations benchmark
Date: Thu, 15 Oct 2026 10:38:17 +0800 [thread overview]
Message-ID: <atA7Mgrupnil4Jcv@mail.google.com> (raw)
In-Reply-To: <as_z4g6CZTObWm2H@google.com>
On Wed, Oct 14, 2026 at 02:28:02PM -0700, Namhyung Kim wrote:
> On Tue, Oct 13, 2026 at 04:56:48PM +0800, Changbin Du wrote:
[..]
> > +
> > +static const struct option options[] = {
> > + OPT_UINTEGER('t', "threads", &threads,
> > + "Number of contending threads (default: 2, or 1 on single CPU)"),
> > + OPT_UINTEGER('r', "runtime", &runtime,
>
> Can you please pick a different short option than 'r'? perf bench
> itself has -r/--repeat option so it would be confusing. It doesn't
> clash for now, but I think it's better to use perf bench options from
> 'perf bench atomic ...' later.
>
Agreed -- I'll drop the short options; --threads and --runtime
stay long-only so no bench option (e.g. -r/--repeat) is shadowed
at perf bench atomic .... Will send an updated patch.
> Otherwise, looks good to me.
>
> Thanks,
> Namhyung
>
>
> > + "Runtime of each repeat in seconds (default: 2)"),
> > + OPT_END()
> > +};
> > +
> > +static const char * const cas_usage[] = {
> > + "perf bench atomic cas <options>",
> > + NULL
> > +};
> > +
> > +static const char * const inc_usage[] = {
> > + "perf bench atomic inc <options>",
> > + NULL
> > +};
> > +
> > +enum atomic_op {
> > + OP_CAS,
> > + OP_INC,
> > +};
> > +
> > +static int bench_op = OP_CAS;
> > +
> > +/*
> > + * The termination flag is polled between batches only, so that the
> > + * check does not perturb the measured loop.
> > + */
> > +#define BATCH_OPS 1024
> > +
> > +static unsigned long shared_counter __aligned(64);
> > +static pthread_barrier_t start_barrier __aligned(64);
> > +static bool done;
> > +
> > +struct worker_stats {
> > + u64 runtime_ns;
> > + u64 ops;
> > +};
> > +
> > +static void cas_batch(void)
> > +{
> > + unsigned long i;
> > +
> > + for (i = 0; i < BATCH_OPS; i++) {
> > + unsigned long old_val, new_val;
> > +
> > + old_val = __atomic_load_n(&shared_counter, __ATOMIC_RELAXED);
> > + do {
> > + new_val = old_val + 1;
> > + } while (!__atomic_compare_exchange_n(&shared_counter, &old_val,
> > + new_val, false,
> > + __ATOMIC_RELAXED,
> > + __ATOMIC_RELAXED));
> > + }
> > +}
> > +
> > +static void inc_batch(void)
> > +{
> > + unsigned long i;
> > +
> > + for (i = 0; i < BATCH_OPS; i++)
> > + __atomic_fetch_add(&shared_counter, 1, __ATOMIC_RELAXED);
> > +}
> > +
> > +static void *worker(void *arg)
> > +{
> > + struct worker_stats *ws = (struct worker_stats *)arg;
> > + struct timespec tstart, tend;
> > + u64 ops = 0;
> > +
> > + pthread_barrier_wait(&start_barrier);
> > +
> > + clock_gettime(CLOCK_MONOTONIC, &tstart);
> > + while (!__atomic_load_n(&done, __ATOMIC_RELAXED)) {
> > + switch (bench_op) {
> > + case OP_CAS:
> > + cas_batch();
> > + break;
> > + case OP_INC:
> > + inc_batch();
> > + break;
> > + default:
> > + BUG_ON(1);
> > + break;
> > + }
> > + ops += BATCH_OPS;
> > + }
> > + clock_gettime(CLOCK_MONOTONIC, &tend);
> > +
> > + ws->runtime_ns = (u64)(tend.tv_sec - tstart.tv_sec) * NSEC_PER_SEC +
> > + (tend.tv_nsec - tstart.tv_nsec);
> > + ws->ops = ops;
> > +
> > + return NULL;
> > +}
> > +
> > +static void print_default_format(struct worker_stats *wstats,
> > + struct stats *throughput_stats)
> > +{
> > + double thru_avg, thru_stddev;
> > +
> > + printf(" Threads: %u, runtime: %u sec, repeats: %u (warmup: 1)\n",
> > + threads, runtime, bench_repeat);
> > +
> > + thru_avg = avg_stats(throughput_stats);
> > + thru_stddev = stddev_stats(throughput_stats);
> > +
> > + printf(" Avg throughput: %'.0f ops/sec (+- %.2f%%)\n",
> > + thru_avg, rel_stddev_stats(thru_stddev, thru_avg));
> > +
> > + /*
> > + * Per-thread operation counts from the last measured repeat show
> > + * whether any thread failed to make progress under contention.
> > + */
> > + if (threads > 1) {
> > + u64 min_ops = UINT64_MAX, max_ops = 0, total_ops = 0;
> > +
> > + for (unsigned int t = 0; t < threads; t++) {
> > + if (wstats[t].ops < min_ops)
> > + min_ops = wstats[t].ops;
> > + if (wstats[t].ops > max_ops)
> > + max_ops = wstats[t].ops;
> > + total_ops += wstats[t].ops;
> > + }
> > +
> > + printf(" Per-thread ops (last repeat):\n");
> > + printf(" fastest: %'" PRIu64 " ops\n", max_ops);
> > + printf(" slowest: %'" PRIu64 " ops\n", min_ops);
> > + printf(" avg: %'" PRIu64 " ops\n", total_ops / threads);
> > + }
> > +}
> > +
> > +static int run_benchmark(void)
> > +{
> > + pthread_t *thread_ids;
> > + struct worker_stats *wstats;
> > + struct stats throughput_stats;
> > + unsigned int r, t;
> > +
> > + thread_ids = calloc(threads, sizeof(pthread_t));
> > + if (!thread_ids)
> > + err(EXIT_FAILURE, "calloc");
> > +
> > + wstats = calloc(threads, sizeof(struct worker_stats));
> > + if (!wstats)
> > + err(EXIT_FAILURE, "calloc");
> > +
> > + init_stats(&throughput_stats);
> > +
> > + for (r = 0; r < bench_repeat + 1; r++) {
> > + u64 max_runtime_ns = 0, total_ops = 0;
> > + double throughput;
> > +
> > + shared_counter = 0;
> > + __atomic_store_n(&done, false, __ATOMIC_RELAXED);
> > + pthread_barrier_init(&start_barrier, NULL, threads);
> > +
> > + for (t = 0; t < threads; t++) {
> > + if (pthread_create(&thread_ids[t], NULL,
> > + worker, &wstats[t]))
> > + err(EXIT_FAILURE, "pthread_create");
> > + }
> > +
> > + sleep(runtime);
> > + __atomic_store_n(&done, true, __ATOMIC_RELAXED);
> > +
> > + for (t = 0; t < threads; t++)
> > + pthread_join(thread_ids[t], NULL);
> > +
> > + pthread_barrier_destroy(&start_barrier);
> > +
> > + for (t = 0; t < threads; t++) {
> > + total_ops += wstats[t].ops;
> > + if (wstats[t].runtime_ns > max_runtime_ns)
> > + max_runtime_ns = wstats[t].runtime_ns;
> > + }
> > +
> > + /*
> > + * Exclude the first repeat (warmup) from statistics so
> > + * that cache-cold and lazy-init effects are not counted.
> > + */
> > + if (r > 0 && max_runtime_ns) {
> > + throughput = (double)total_ops /
> > + ((double)max_runtime_ns / NSEC_PER_SEC);
> > + update_stats(&throughput_stats, (u64)throughput);
> > + }
> > + }
> > +
> > + switch (bench_format) {
> > + case BENCH_FORMAT_DEFAULT:
> > + print_default_format(wstats, &throughput_stats);
> > + break;
> > +
> > + case BENCH_FORMAT_SIMPLE:
> > + printf("%.0f\n", avg_stats(&throughput_stats));
> > + break;
> > +
> > + default:
> > + fprintf(stderr, "Unknown format: %d\n", bench_format);
> > + exit(EXIT_FAILURE);
> > + }
> > +
> > + free(thread_ids);
> > + free(wstats);
> > +
> > + return 0;
> > +}
> > +
> > +static int run_atomic_bench(int argc, const char **argv,
> > + const char * const *usage)
> > +{
> > + struct perf_cpu_map *cpu;
> > +
> > + if (parse_options(argc, argv, options, usage, 0)) {
> > + usage_with_options(usage, options);
> > + exit(EXIT_FAILURE);
> > + }
> > +
> > + if (!threads) { /* default to 2 threads (1 on a single-CPU machine) */
> > + cpu = perf_cpu_map__new_online_cpus();
> > + threads = (cpu && perf_cpu_map__nr(cpu) > 1) ? 2 : 1;
> > + perf_cpu_map__put(cpu);
> > + }
> > +
> > + if (runtime < 1) {
> > + fprintf(stderr, "Invalid runtime: %u\n", runtime);
> > + return 1;
> > + }
> > +
> > + return run_benchmark();
> > +}
> > +
> > +int bench_atomic_cas(int argc, const char **argv)
> > +{
> > + bench_op = OP_CAS;
> > + return run_atomic_bench(argc, argv, cas_usage);
> > +}
> > +
> > +int bench_atomic_inc(int argc, const char **argv)
> > +{
> > + bench_op = OP_INC;
> > + return run_atomic_bench(argc, argv, inc_usage);
> > +}
> > +#endif /* HAVE_PTHREAD_BARRIER */
> > diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
> > index 8519eb5a42fa..85e3ac471875 100644
> > --- a/tools/perf/bench/bench.h
> > +++ b/tools/perf/bench/bench.h
> > @@ -30,6 +30,8 @@ int bench_mem_memcpy(int argc, const char **argv);
> > int bench_mem_memset(int argc, const char **argv);
> > int bench_mem_mmap(int argc, const char **argv);
> > int bench_mem_find_bit(int argc, const char **argv);
> > +int bench_atomic_cas(int argc, const char **argv);
> > +int bench_atomic_inc(int argc, const char **argv);
> > int bench_futex_hash(int argc, const char **argv);
> > int bench_futex_wake(int argc, const char **argv);
> > int bench_futex_wake_parallel(int argc, const char **argv);
> > diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c
> > index 02d47913cc6a..62bebdedd3b8 100644
> > --- a/tools/perf/builtin-bench.c
> > +++ b/tools/perf/builtin-bench.c
> > @@ -14,6 +14,7 @@
> > * syscall ... System call performance
> > * mem ... memory access performance
> > * numa ... NUMA scheduling and MM performance
> > + * atomic ... Atomic operation performance
> > * futex ... Futex performance
> > * epoll ... Event poll performance
> > */
> > @@ -115,6 +116,13 @@ static const struct bench uprobe_benchmarks[] = {
> > { NULL, NULL, NULL },
> > };
> >
> > +static const struct bench atomic_benchmarks[] = {
> > + { "cas", "Benchmark CAS (compare-and-swap) operations", bench_atomic_cas },
> > + { "inc", "Benchmark atomic fetch-add operations", bench_atomic_inc },
> > + { "all", "Run all atomic benchmarks", NULL },
> > + { NULL, NULL, NULL }
> > +};
> > +
> > struct collection {
> > const char *name;
> > const char *summary;
> > @@ -128,6 +136,7 @@ static const struct collection collections[] = {
> > #ifdef HAVE_LIBNUMA_SUPPORT
> > { "numa", "NUMA scheduling and MM benchmarks", numa_benchmarks },
> > #endif
> > + { "atomic", "Atomic operation benchmarks", atomic_benchmarks },
> > {"futex", "Futex stressing benchmarks", futex_benchmarks },
> > #ifdef HAVE_EVENTFD_SUPPORT
> > {"epoll", "Epoll stressing benchmarks", epoll_benchmarks },
> > --
> > 2.53.0
> >
--
Cheers,
Changbin Du
next prev parent reply other threads:[~2026-10-15 2:38 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-13 8:56 [PATCH v4] perf bench: Add atomic operations benchmark Changbin Du
2026-10-13 9:06 ` sashiko-bot
2026-10-14 21:28 ` Namhyung Kim
2026-10-15 2:38 ` Changbin Du [this message]
2026-10-15 4:07 ` Namhyung Kim
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=atA7Mgrupnil4Jcv@mail.google.com \
--to=changbin.du@gmail.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=alexander.shishkin@linux.intel.com \
--cc=david.laight.linux@gmail.com \
--cc=irogers@google.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.