From 9de473a8c2ca5256a47e245c057a495b7ba9f193 Mon Sep 17 00:00:00 2001 From: Erwan Velu Date: Thu, 7 Jan 2021 00:34:06 +0100 Subject: [PATCH] engines/cpu: Adding qsort capabilities This commit adds cpumode option into the cpuio engine. By default, cpumode=noop to keep the current behavior. If cpumode is set to qsort, fio will use a qsort algorithm instead of the noop instructions to load the processor. This mode will consume more cpu power and will be useful to increase the pressure on the thermal and electrical components. The expected cpu load is selected as per noop via the cpuload option. qsort() consumes a lot of energy so the duration of every loop will vary over time as the power management & cpu clock changes. To ensure a proper calibration, the thinktime is adjusted after every qsort() computation to be as precise as possible. To give an order of magnitude, on an AMD 7502P (TDP=180W) : cpuload=30, numjobs=64: packagewatt = 134W cpuload=50, numjobs=64: packagewatt = 167W cpuload=70, numjobs=64: packagewatt = 180W The example file is updated to reflect this new capabilities. The qsort code is coming from stress-qsort.c from stress-ng tool. This software is also GPLv2 but author was informed and agreed with this usage. Signed-off-by: Erwan Velu --- HOWTO | 6 +- engines/cpu.c | 205 ++++++++++++++++++++++++++++++++++++++++++++- examples/cpuio.fio | 14 +++- fio.1 | 21 +++-- 4 files changed, 233 insertions(+), 13 deletions(-) diff --git a/HOWTO b/HOWTO index d663166d..0547c721 100644 --- a/HOWTO +++ b/HOWTO @@ -1912,12 +1912,14 @@ I/O engine **cpuio** Doesn't transfer any data, but burns CPU cycles according to the - :option:`cpuload` and :option:`cpuchunks` options. Setting - :option:`cpuload`\=85 will cause that job to do nothing but burn 85% + :option:`cpuload`, :option:`cpuchunks` and :option:`cpumode` options. + Setting :option:`cpuload`\=85 will cause that job to do nothing but burn 85% of the CPU. In case of SMP machines, use :option:`numjobs`\= to get desired CPU usage, as the cpuload only loads a single CPU at the desired rate. A job never finishes unless there is at least one non-cpuio job. + Setting :option:`cpumode`\=qsort replace the default noop instructions loop + by a qsort algorithm to consume more energy. **rdma** The RDMA I/O engine supports both RDMA memory semantics diff --git a/engines/cpu.c b/engines/cpu.c index 4d572b44..9d57beff 100644 --- a/engines/cpu.c +++ b/engines/cpu.c @@ -8,11 +8,26 @@ #include "../fio.h" #include "../optgroup.h" +// number of 32 bit integers to sort +size_t qsort_size = (256 * (1ULL << 10)); // 256KB + +struct mwc_t { + uint32_t w; + uint32_t z; +}; + +enum stress_mode { + FIO_CPU_NOOP = 0, + FIO_CPU_QSORT = 1, +}; + struct cpu_options { void *pad; unsigned int cpuload; unsigned int cpucycle; + enum stress_mode cpumode; unsigned int exit_io_done; + int32_t *qsort_data; }; static struct fio_option options[] = { @@ -25,6 +40,26 @@ static struct fio_option options[] = { .category = FIO_OPT_C_ENGINE, .group = FIO_OPT_G_INVALID, }, + { + .name = "cpumode", + .lname = "cpumode", + .type = FIO_OPT_STR, + .help = "Stress mode", + .off1 = offsetof(struct cpu_options, cpumode), + .def = "noop", + .posval = { + { .ival = "noop", + .oval = FIO_CPU_NOOP, + .help = "NOOP instructions", + }, + { .ival = "qsort", + .oval = FIO_CPU_QSORT, + .help = "QSORT computation", + }, + }, + .category = FIO_OPT_C_ENGINE, + .group = FIO_OPT_G_INVALID, + }, { .name = "cpuchunks", .lname = "CPU chunk", @@ -52,6 +87,88 @@ static struct fio_option options[] = { }, }; +/* + * mwc32() + * Multiply-with-carry random numbers + * fast pseudo random number generator, see + * http://www.cse.yorku.ca/~oz/marsaglia-rng.html + */ +uint32_t mwc32(struct mwc_t *mwc) +{ + mwc->z = 36969 * (mwc->z & 65535) + (mwc->z >> 16); + mwc->w = 18000 * (mwc->w & 65535) + (mwc->w >> 16); + return (mwc->z << 16) + mwc->w; +} + +/* + * stress_qsort_cmp_1() + * qsort comparison - sort on int32 values + */ +static int stress_qsort_cmp_1(const void *p1, const void *p2) +{ + const int32_t *i1 = (const int32_t *)p1; + const int32_t *i2 = (const int32_t *)p2; + + if (*i1 > *i2) + return 1; + else if (*i1 < *i2) + return -1; + else + return 0; +} + +/* + * stress_qsort_cmp_2() + * qsort comparison - reverse sort on int32 values + */ +static int stress_qsort_cmp_2(const void *p1, const void *p2) +{ + return stress_qsort_cmp_1(p2, p1); +} + +/* + * stress_qsort_cmp_3() + * qsort comparison - sort on int8 values + */ +static int stress_qsort_cmp_3(const void *p1, const void *p2) +{ + const int8_t *i1 = (const int8_t *)p1; + const int8_t *i2 = (const int8_t *)p2; + + /* Force re-ordering on 8 bit value */ + return *i1 - *i2; +} + +static int do_qsort(struct thread_data *td) +{ + struct thread_options *o = &td->o; + struct cpu_options *co = td->eo; + struct timespec start, now; + fio_get_mono_time(&start); + /* Sort "random" data */ + qsort(co->qsort_data, qsort_size, sizeof(*(co->qsort_data)), stress_qsort_cmp_1); + + /* Reverse sort */ + qsort(co->qsort_data, qsort_size, sizeof(*(co->qsort_data)), stress_qsort_cmp_2); + + /* And re-order by byte compare */ + qsort((uint8_t *)co->qsort_data, qsort_size * 4, sizeof(uint8_t), stress_qsort_cmp_3); + + /* Reverse sort this again */ + qsort(co->qsort_data, qsort_size, sizeof(*(co->qsort_data)), stress_qsort_cmp_2); + fio_get_mono_time(&now); + + /* Adjusting cpucycle automatically to be as close as possible to the expected cpuload + * The time to execute do_qsort() may change over time as per : + * - the job concurrency + * - the cpu clock adjusted by the power management + * After every do_qsort() call, the next thinktime is adjusted regarding the last run performance + */ + co->cpucycle = utime_since(&start, &now); + o->thinktime = ((unsigned long long) co->cpucycle * (100 - co->cpuload)) / co->cpuload; + + return 0; +} static enum fio_q_status fio_cpuio_queue(struct thread_data *td, struct io_u fio_unused *io_u) @@ -63,14 +180,66 @@ static enum fio_q_status fio_cpuio_queue(struct thread_data *td, return FIO_Q_BUSY; } - usec_spin(co->cpucycle); + switch (co->cpumode) { + case FIO_CPU_NOOP: + usec_spin(co->cpucycle); + break; + case FIO_CPU_QSORT: + do_qsort(td); + break; + } + return FIO_Q_COMPLETED; } +static int noop_init(struct thread_data *td) +{ + struct cpu_options *co = td->eo; + + log_info("%s (noop): ioengine=%s, cpuload=%u, cpucycle=%u\n", + td->o.name, td->io_ops->name, co->cpuload, co->cpucycle); + return 0; +} + +static int qsort_cleanup(struct thread_data *td) +{ + struct cpu_options *co = td->eo; + if (co->qsort_data) + free(co->qsort_data); + return 0; +} + +static int qsort_init(struct thread_data *td) +{ + struct cpu_options *co = td->eo; + + // Setting up a default entropy + struct mwc_t mwc = { + (521288629UL), + (362436069UL) + }; + + co->qsort_data = calloc(qsort_size, sizeof(*co->qsort_data)); + if (co->qsort_data == NULL) { + td_verror(td, ENOMEM, "qsort_init"); + return 1; + } + + /* This is expensive, init the memory once */ + for (int32_t *ptr = co->qsort_data, i = 0; i < qsort_size; i++) + *ptr++ = mwc32(&mwc); + + log_info("%s (qsort): ioengine=%s, cpuload=%u, cpucycle=%u\n", + td->o.name, td->io_ops->name, co->cpuload, co->cpucycle); + + return 0; +} + static int fio_cpuio_init(struct thread_data *td) { struct thread_options *o = &td->o; struct cpu_options *co = td->eo; + int td_previous_state; if (!co->cpuload) { td_vmsg(td, EINVAL, "cpu thread needs rate (cpuload=)","cpuio"); @@ -80,6 +249,14 @@ static int fio_cpuio_init(struct thread_data *td) if (co->cpuload > 100) co->cpuload = 100; + // Saving the current thread state + td_previous_state = td->runstate; + /* Reporting that we are preparing the engine + * This is useful as the qsort() calibration takes time + * This prevents the job from starting before init is completed + */ + td_set_runstate(td, TD_SETTING_UP); + /* * set thinktime_sleep and thinktime_spin appropriately */ @@ -89,12 +266,33 @@ static int fio_cpuio_init(struct thread_data *td) o->nr_files = o->open_files = 1; - log_info("%s: ioengine=%s, cpuload=%u, cpucycle=%u\n", - td->o.name, td->io_ops->name, co->cpuload, co->cpucycle); + switch (co->cpumode) { + case FIO_CPU_NOOP: + noop_init(td); + break; + case FIO_CPU_QSORT: + qsort_init(td); + break; + } + // Let's restore the previous state. + td_set_runstate(td, td_previous_state); return 0; } +static void fio_cpuio_cleanup(struct thread_data *td) +{ + struct cpu_options *co = td->eo; + + switch (co->cpumode) { + case FIO_CPU_NOOP: + break; + case FIO_CPU_QSORT: + qsort_cleanup(td); + break; + } +} + static int fio_cpuio_open(struct thread_data fio_unused *td, struct fio_file fio_unused *f) { @@ -106,6 +304,7 @@ static struct ioengine_ops ioengine = { .version = FIO_IOOPS_VERSION, .queue = fio_cpuio_queue, .init = fio_cpuio_init, + .cleanup = fio_cpuio_cleanup, .open_file = fio_cpuio_open, .flags = FIO_SYNCIO | FIO_DISKLESSIO | FIO_NOIO, .options = options, diff --git a/examples/cpuio.fio b/examples/cpuio.fio index 577e0729..471cf4b2 100644 --- a/examples/cpuio.fio +++ b/examples/cpuio.fio @@ -1,8 +1,18 @@ [global] ioengine=cpuio time_based -runtime=10 +runtime=15 -[burn50percent] +# The following example load 2 cores at 50% with the noop (default) mode +[burn_2x50_noop] cpuload=50 +numjobs=2 +cpumode=noop +# Once burn_2x50_noop is over, +# fio load 2 cores at 50% with the qsort mode which drains much more power +[burn_2x50%_qsort] +stonewall +cpuload=50 +numjobs=2 +cpumode=qsort diff --git a/fio.1 b/fio.1 index b29ac437..e361b05f 100644 --- a/fio.1 +++ b/fio.1 @@ -1690,12 +1690,21 @@ This engine defines engine specific options. .TP .B cpuio Doesn't transfer any data, but burns CPU cycles according to the -\fBcpuload\fR and \fBcpuchunks\fR options. Setting -\fBcpuload\fR\=85 will cause that job to do nothing but burn 85% -of the CPU. In case of SMP machines, use `numjobs=' -to get desired CPU usage, as the cpuload only loads a -single CPU at the desired rate. A job never finishes unless there is -at least one non-cpuio job. +\fBcpuload\fR, \fBcpuchunks\fR and \fBcpumode\fR options. +A job never finishes unless there is at least one non-cpuio job. +.RS +.P +.PD 0 +\fBcpuload\fR\=85 will cause that job to do nothing but burn 85% of the CPU. +In case of SMP machines, use \fBnumjobs=\fR\ to get desired CPU usage, +as the cpuload only loads a single CPU at the desired rate. + +.P +\fBcpumode\fR\=qsort replace the default noop instructions loop +by a qsort algorithm to consume more energy. + +.P +.RE .TP .B rdma The RDMA I/O engine supports both RDMA memory semantics -- 2.25.1