blob: 67901a12ec6bd6c531caceab29646946872c4f8b [file] [log] [blame]
/*
* counttorture.h: simple user-level performance/stress test of counters.
*
* Usage:
* ./countxxx <nreaders> rperf [ <cpustride> ]
* Run a read-side performance test with the specified
* number of counters, running on CPUs spaced by <cpustride>.
* Thus "./count 16 rperf 2" would run 16 readers on even-numbered
* CPUs from 0 to 30.
* ./countxxx <nupdaters> uperf [ <cpustride> ]
* Run an update-side performance test with the specified
* number of updaters and specified CPU spacing.
* ./countxxx <nupdaters> perf [ <cpustride> ]
* Run a combined read/update performance test with the specified
* number of readers and one updater and specified CPU spacing.
* The readers run on the low-numbered CPUs and the updater
* of the highest-numbered CPU.
*
* The above tests produce output as follows:
*
* n_reads: 824000 n_updates: 75264000 nreaders: 1 nupdaters: 1 duration: 240
* ns/read: 291.262 ns/update: 3.18878
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*
* Copyright (c) 2009-2019 Paul E. McKenney, IBM Corporation.
* Copyright (c) 2019 Paul E. McKenney, Facebook.
*/
/*
* Test variables.
*/
DEFINE_PER_THREAD(long long, n_reads_pt);
DEFINE_PER_THREAD(long long, n_updates_pt);
long long n_reads = 0LL;
long long n_updates = 0LL;
atomic_t nthreadsrunning;
int nthreadsexpected;
#define GOFLAG_INIT 0
#define GOFLAG_RUN 1
#define GOFLAG_STOP 2
int goflag __attribute__((__aligned__(CACHE_LINE_SIZE))) = GOFLAG_INIT;
#define COUNT_READ_RUN 1000
#define COUNT_UPDATE_RUN 1000
#ifndef NEED_REGISTER_THREAD
#define count_register_thread(p) do ; while (0)
#define count_unregister_thread(n) do ; while (0)
#endif /* #ifndef NEED_REGISTER_THREAD */
#ifndef KEEP_GCC_THREAD_LOCAL
#define _wait_all_threads() wait_all_threads()
#define _count_unregister_thread(n) count_unregister_thread(n)
#define final_wait_all_threads()
#else /* #ifndef KEEP_GCC_THREAD_LOCAL */
#define _wait_all_threads() { \
while (READ_ONCE(finalthreadcount) < nthreadsexpected) \
poll(NULL, 0, 1);}
#define _count_unregister_thread(n) count_unregister_thread(n + 1)
#define final_wait_all_threads() { \
WRITE_ONCE(finalthreadcount, nthreadsexpected + 1); \
wait_all_threads();}
#endif /* #ifndef KEEP_GCC_THREAD_LOCAL */
unsigned long garbage = 0; /* disable compiler optimizations. */
/*
* Performance test.
*/
void *count_read_perf_test(void *arg)
{
int i;
unsigned long j = 0;
unsigned long __maybe_unused k = 0;
int me = (long)arg;
long long n_reads_local = 0LL;
run_on(me);
count_register_thread(&k);
atomic_inc(&nthreadsrunning);
while (READ_ONCE(goflag) == GOFLAG_INIT)
poll(NULL, 0, 1);
while (READ_ONCE(goflag) == GOFLAG_RUN) {
for (i = COUNT_READ_RUN; i > 0; i--) {
j += read_count();
}
n_reads_local += COUNT_READ_RUN;
}
__get_thread_var(n_reads_pt) += n_reads_local;
_count_unregister_thread(nthreadsexpected);
garbage += j;
return (NULL);
}
void *count_update_perf_test(void *arg)
{
int i;
unsigned long __maybe_unused k = 0;
long long n_updates_local = 0LL;
count_register_thread(&k);
atomic_inc(&nthreadsrunning);
while (READ_ONCE(goflag) == GOFLAG_INIT)
poll(NULL, 0, 1);
while (READ_ONCE(goflag) == GOFLAG_RUN) {
for (i = COUNT_UPDATE_RUN; i > 0; i--) {
inc_count();
}
n_updates_local += COUNT_UPDATE_RUN;
}
__get_thread_var(n_updates_pt) += n_updates_local;
_count_unregister_thread(nthreadsexpected);
return NULL;
}
void perftestinit(int nthreads)
{
init_per_thread(n_reads_pt, 0LL);
init_per_thread(n_updates_pt, 0LL);
atomic_set(&nthreadsrunning, 0);
nthreadsexpected = nthreads;
}
void perftestrun(int nthreads, int nreaders, int nupdaters)
{
int exitcode = EXIT_SUCCESS;
int t;
int duration = 240;
smp_mb();
while (atomic_read(&nthreadsrunning) < nthreads)
poll(NULL, 0, 1);
goflag = GOFLAG_RUN;
smp_mb();
poll(NULL, 0, duration);
smp_mb();
goflag = GOFLAG_STOP;
smp_mb();
_wait_all_threads();
count_cleanup();
for_each_thread(t) {
n_reads += per_thread(n_reads_pt, t);
n_updates += per_thread(n_updates_pt, t);
#ifdef CHECK_DISTRIBUTION
printf("%d: r: %lld u: %lld\n", t,
per_thread(n_reads_pt, t), per_thread(n_updates_pt, t));
#endif /* #ifdef CHECK_DISTRIBUTION */
}
if (n_updates != read_count()) {
printf("!!! Count mismatch: %lld counted vs. %lu final value\n",
n_updates, read_count());
exitcode = EXIT_FAILURE;
}
printf("n_reads: %lld n_updates: %lld nreaders: %d nupdaters: %d duration: %d\n",
n_reads, n_updates, nreaders, nupdaters, duration);
printf("ns/read: %g ns/update: %g\n",
((duration * 1000*1000.*(double)nreaders) /
(double)n_reads),
((duration * 1000*1000.*(double)nupdaters) /
(double)n_updates));
final_wait_all_threads();
exit(exitcode);
}
void perftest(int nwriters, int cpustride)
{
int i;
long arg;
perftestinit(nwriters + 1);
for (i = 0; i < nwriters; i++) {
arg = (long)(i * cpustride);
create_thread(count_update_perf_test, (void *)arg);
}
arg = (long)(i * cpustride);
create_thread(count_read_perf_test, (void *)arg);
perftestrun(i + 1, 1, nwriters);
}
void rperftest(int nreaders, int cpustride)
{
int i;
long arg;
perftestinit(nreaders);
init_per_thread(n_reads_pt, 0LL);
for (i = 0; i < nreaders; i++) {
arg = (long)(i * cpustride);
create_thread(count_read_perf_test, (void *)arg);
}
perftestrun(i, nreaders, 0);
}
void uperftest(int nupdaters, int cpustride)
{
int i;
long arg;
perftestinit(nupdaters);
init_per_thread(n_reads_pt, 0LL);
for (i = 0; i < nupdaters; i++) {
arg = (long)(i * cpustride);
create_thread(count_update_perf_test, (void *)arg);
}
perftestrun(i, 0, nupdaters);
}
/*
* Mainprogram.
*/
void usage(int argc, char *argv[])
{
fprintf(stderr,
"Usage: %s [nreaders [ perf [ cpustride ] ] ]\n", argv[0]);
fprintf(stderr,
"Usage: %s [nreaders [ rperf [ cpustride ] ] ]\n", argv[0]);
fprintf(stderr,
"Usage: %s [nupdaters [ uperf [ cpustride ] ] ]\n", argv[0]);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
int nreaders = 1;
int cpustride = 1;
smp_init();
count_init();
if (argc > 1) {
nreaders = strtoul(argv[1], NULL, 0);
if (argc == 2)
perftest(nreaders, cpustride);
if (argc > 3)
cpustride = strtoul(argv[3], NULL, 0);
if (strcmp(argv[2], "perf") == 0)
perftest(nreaders, cpustride);
else if (strcmp(argv[2], "rperf") == 0)
rperftest(nreaders, cpustride);
else if (strcmp(argv[2], "uperf") == 0)
uperftest(nreaders, cpustride);
usage(argc, argv);
}
perftest(nreaders, cpustride);
return 0;
}