Skip to content

Commit e6e78b0

Browse files
joelagnelpaulmckrcu
authored andcommitted
rcuperf: Add kfree_rcu() performance Tests
This test runs kfree_rcu() in a loop to measure performance of the new kfree_rcu() batching functionality. The following table shows results when booting with arguments: rcuperf.kfree_loops=20000 rcuperf.kfree_alloc_num=8000 rcuperf.kfree_rcu_test=1 rcuperf.kfree_no_batch=X rcuperf.kfree_no_batch=X # Grace Periods Test Duration (s) X=1 (old behavior) 9133 11.5 X=0 (new behavior) 1732 12.5 On a 16 CPU system with the above boot parameters, we see that the total number of grace periods that elapse during the test drops from 9133 when not batching to 1732 when batching (a 5X improvement). The kfree_rcu() flood itself slows down a bit when batching, though, as shown. Note that the active memory consumption during the kfree_rcu() flood does increase to around 200-250MB due to the batching (from around 50MB without batching). However, this memory consumption is relatively constant. In other words, the system is able to keep up with the kfree_rcu() load. The memory consumption comes down considerably if KFREE_DRAIN_JIFFIES is increased from HZ/50 to HZ/80. A later patch will reduce memory consumption further by using multiple lists. Also, when running the test, please disable CONFIG_DEBUG_PREEMPT and CONFIG_PROVE_RCU for realistic comparisons with/without batching. Signed-off-by: Joel Fernandes (Google) <[email protected]> Signed-off-by: Paul E. McKenney <[email protected]>
1 parent a35d169 commit e6e78b0

File tree

2 files changed

+190
-8
lines changed

2 files changed

+190
-8
lines changed

Documentation/admin-guide/kernel-parameters.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3978,6 +3978,23 @@
39783978
test until boot completes in order to avoid
39793979
interference.
39803980

3981+
rcuperf.kfree_rcu_test= [KNL]
3982+
Set to measure performance of kfree_rcu() flooding.
3983+
3984+
rcuperf.kfree_nthreads= [KNL]
3985+
The number of threads running loops of kfree_rcu().
3986+
3987+
rcuperf.kfree_alloc_num= [KNL]
3988+
Number of allocations and frees done in an iteration.
3989+
3990+
rcuperf.kfree_loops= [KNL]
3991+
Number of loops doing rcuperf.kfree_alloc_num number
3992+
of allocations and frees.
3993+
3994+
rcuperf.kfree_no_batch= [KNL]
3995+
Use the non-batching (less efficient) version of kfree_rcu().
3996+
This is useful for comparing with the batched version.
3997+
39813998
rcuperf.nreaders= [KNL]
39823999
Set number of RCU readers. The value -1 selects
39834000
N, where N is the number of CPUs. A value

kernel/rcu/rcuperf.c

Lines changed: 173 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ torture_param(bool, shutdown, RCUPERF_SHUTDOWN,
8686
"Shutdown at end of performance tests.");
8787
torture_param(int, verbose, 1, "Enable verbose debugging printk()s");
8888
torture_param(int, writer_holdoff, 0, "Holdoff (us) between GPs, zero to disable");
89+
torture_param(int, kfree_rcu_test, 0, "Do we run a kfree_rcu() perf test?");
8990

9091
static char *perf_type = "rcu";
9192
module_param(perf_type, charp, 0444);
@@ -105,8 +106,8 @@ static atomic_t n_rcu_perf_writer_finished;
105106
static wait_queue_head_t shutdown_wq;
106107
static u64 t_rcu_perf_writer_started;
107108
static u64 t_rcu_perf_writer_finished;
108-
static unsigned long b_rcu_perf_writer_started;
109-
static unsigned long b_rcu_perf_writer_finished;
109+
static unsigned long b_rcu_gp_test_started;
110+
static unsigned long b_rcu_gp_test_finished;
110111
static DEFINE_PER_CPU(atomic_t, n_async_inflight);
111112

112113
#define MAX_MEAS 10000
@@ -378,10 +379,10 @@ rcu_perf_writer(void *arg)
378379
if (atomic_inc_return(&n_rcu_perf_writer_started) >= nrealwriters) {
379380
t_rcu_perf_writer_started = t;
380381
if (gp_exp) {
381-
b_rcu_perf_writer_started =
382+
b_rcu_gp_test_started =
382383
cur_ops->exp_completed() / 2;
383384
} else {
384-
b_rcu_perf_writer_started = cur_ops->get_gp_seq();
385+
b_rcu_gp_test_started = cur_ops->get_gp_seq();
385386
}
386387
}
387388

@@ -429,10 +430,10 @@ rcu_perf_writer(void *arg)
429430
PERFOUT_STRING("Test complete");
430431
t_rcu_perf_writer_finished = t;
431432
if (gp_exp) {
432-
b_rcu_perf_writer_finished =
433+
b_rcu_gp_test_finished =
433434
cur_ops->exp_completed() / 2;
434435
} else {
435-
b_rcu_perf_writer_finished =
436+
b_rcu_gp_test_finished =
436437
cur_ops->get_gp_seq();
437438
}
438439
if (shutdown) {
@@ -515,8 +516,8 @@ rcu_perf_cleanup(void)
515516
t_rcu_perf_writer_finished -
516517
t_rcu_perf_writer_started,
517518
ngps,
518-
rcuperf_seq_diff(b_rcu_perf_writer_finished,
519-
b_rcu_perf_writer_started));
519+
rcuperf_seq_diff(b_rcu_gp_test_finished,
520+
b_rcu_gp_test_started));
520521
for (i = 0; i < nrealwriters; i++) {
521522
if (!writer_durations)
522523
break;
@@ -584,6 +585,167 @@ rcu_perf_shutdown(void *arg)
584585
return -EINVAL;
585586
}
586587

588+
/*
589+
* kfree_rcu() performance tests: Start a kfree_rcu() loop on all CPUs for number
590+
* of iterations and measure total time and number of GP for all iterations to complete.
591+
*/
592+
593+
torture_param(int, kfree_nthreads, -1, "Number of threads running loops of kfree_rcu().");
594+
torture_param(int, kfree_alloc_num, 8000, "Number of allocations and frees done in an iteration.");
595+
torture_param(int, kfree_loops, 10, "Number of loops doing kfree_alloc_num allocations and frees.");
596+
torture_param(int, kfree_no_batch, 0, "Use the non-batching (slower) version of kfree_rcu().");
597+
598+
static struct task_struct **kfree_reader_tasks;
599+
static int kfree_nrealthreads;
600+
static atomic_t n_kfree_perf_thread_started;
601+
static atomic_t n_kfree_perf_thread_ended;
602+
603+
struct kfree_obj {
604+
char kfree_obj[8];
605+
struct rcu_head rh;
606+
};
607+
608+
static int
609+
kfree_perf_thread(void *arg)
610+
{
611+
int i, loop = 0;
612+
long me = (long)arg;
613+
struct kfree_obj *alloc_ptr;
614+
u64 start_time, end_time;
615+
616+
VERBOSE_PERFOUT_STRING("kfree_perf_thread task started");
617+
set_cpus_allowed_ptr(current, cpumask_of(me % nr_cpu_ids));
618+
set_user_nice(current, MAX_NICE);
619+
620+
start_time = ktime_get_mono_fast_ns();
621+
622+
if (atomic_inc_return(&n_kfree_perf_thread_started) >= kfree_nrealthreads) {
623+
if (gp_exp)
624+
b_rcu_gp_test_started = cur_ops->exp_completed() / 2;
625+
else
626+
b_rcu_gp_test_started = cur_ops->get_gp_seq();
627+
}
628+
629+
do {
630+
for (i = 0; i < kfree_alloc_num; i++) {
631+
alloc_ptr = kmalloc(sizeof(struct kfree_obj), GFP_KERNEL);
632+
if (!alloc_ptr)
633+
return -ENOMEM;
634+
635+
if (!kfree_no_batch) {
636+
kfree_rcu(alloc_ptr, rh);
637+
} else {
638+
rcu_callback_t cb;
639+
640+
cb = (rcu_callback_t)(unsigned long)offsetof(struct kfree_obj, rh);
641+
kfree_call_rcu_nobatch(&(alloc_ptr->rh), cb);
642+
}
643+
}
644+
645+
cond_resched();
646+
} while (!torture_must_stop() && ++loop < kfree_loops);
647+
648+
if (atomic_inc_return(&n_kfree_perf_thread_ended) >= kfree_nrealthreads) {
649+
end_time = ktime_get_mono_fast_ns();
650+
651+
if (gp_exp)
652+
b_rcu_gp_test_finished = cur_ops->exp_completed() / 2;
653+
else
654+
b_rcu_gp_test_finished = cur_ops->get_gp_seq();
655+
656+
pr_alert("Total time taken by all kfree'ers: %llu ns, loops: %d, batches: %ld\n",
657+
(unsigned long long)(end_time - start_time), kfree_loops,
658+
rcuperf_seq_diff(b_rcu_gp_test_finished, b_rcu_gp_test_started));
659+
if (shutdown) {
660+
smp_mb(); /* Assign before wake. */
661+
wake_up(&shutdown_wq);
662+
}
663+
}
664+
665+
torture_kthread_stopping("kfree_perf_thread");
666+
return 0;
667+
}
668+
669+
static void
670+
kfree_perf_cleanup(void)
671+
{
672+
int i;
673+
674+
if (torture_cleanup_begin())
675+
return;
676+
677+
if (kfree_reader_tasks) {
678+
for (i = 0; i < kfree_nrealthreads; i++)
679+
torture_stop_kthread(kfree_perf_thread,
680+
kfree_reader_tasks[i]);
681+
kfree(kfree_reader_tasks);
682+
}
683+
684+
torture_cleanup_end();
685+
}
686+
687+
/*
688+
* shutdown kthread. Just waits to be awakened, then shuts down system.
689+
*/
690+
static int
691+
kfree_perf_shutdown(void *arg)
692+
{
693+
do {
694+
wait_event(shutdown_wq,
695+
atomic_read(&n_kfree_perf_thread_ended) >=
696+
kfree_nrealthreads);
697+
} while (atomic_read(&n_kfree_perf_thread_ended) < kfree_nrealthreads);
698+
699+
smp_mb(); /* Wake before output. */
700+
701+
kfree_perf_cleanup();
702+
kernel_power_off();
703+
return -EINVAL;
704+
}
705+
706+
static int __init
707+
kfree_perf_init(void)
708+
{
709+
long i;
710+
int firsterr = 0;
711+
712+
kfree_nrealthreads = compute_real(kfree_nthreads);
713+
/* Start up the kthreads. */
714+
if (shutdown) {
715+
init_waitqueue_head(&shutdown_wq);
716+
firsterr = torture_create_kthread(kfree_perf_shutdown, NULL,
717+
shutdown_task);
718+
if (firsterr)
719+
goto unwind;
720+
schedule_timeout_uninterruptible(1);
721+
}
722+
723+
kfree_reader_tasks = kcalloc(kfree_nrealthreads, sizeof(kfree_reader_tasks[0]),
724+
GFP_KERNEL);
725+
if (kfree_reader_tasks == NULL) {
726+
firsterr = -ENOMEM;
727+
goto unwind;
728+
}
729+
730+
for (i = 0; i < kfree_nrealthreads; i++) {
731+
firsterr = torture_create_kthread(kfree_perf_thread, (void *)i,
732+
kfree_reader_tasks[i]);
733+
if (firsterr)
734+
goto unwind;
735+
}
736+
737+
while (atomic_read(&n_kfree_perf_thread_started) < kfree_nrealthreads)
738+
schedule_timeout_uninterruptible(1);
739+
740+
torture_init_end();
741+
return 0;
742+
743+
unwind:
744+
torture_init_end();
745+
kfree_perf_cleanup();
746+
return firsterr;
747+
}
748+
587749
static int __init
588750
rcu_perf_init(void)
589751
{
@@ -616,6 +778,9 @@ rcu_perf_init(void)
616778
if (cur_ops->init)
617779
cur_ops->init();
618780

781+
if (kfree_rcu_test)
782+
return kfree_perf_init();
783+
619784
nrealwriters = compute_real(nwriters);
620785
nrealreaders = compute_real(nreaders);
621786
atomic_set(&n_rcu_perf_reader_started, 0);

0 commit comments

Comments
 (0)