Every vCPU now uses a separate set of TBs for each set of dynamic tracing event state values. Each set of TBs can be used by any number of vCPUs to maximize TB reuse when vCPUs have the same tracing state. This feature is later used by tracetool to optimize tracing of guest code events. The maximum number of TB sets is defined as 2^E, where E is the number of events that have the 'vcpu' property (their state is stored in CPUState->trace_dstate). For this to work, a change on the dynamic tracing state of a vCPU will force it to flush its virtual TB cache (which is only indexed by address), and fall back to the physical TB cache (which now contains the vCPU's dynamic tracing state as part of the hashing function). Signed-off-by: Lluís Vilanova <vilanova@ac.upc.edu> Reviewed-by: Richard Henderson <rth@twiddle.net> Reviewed-by: Emilio G. Cota <cota@braap.org> Signed-off-by: Emilio G. Cota <cota@braap.org> Message-id: 149915775266.6295.10060144081246467690.stgit@frigg.lan Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
488 lines
12 KiB
C
488 lines
12 KiB
C
/*
|
|
* Copyright (C) 2016, Emilio G. Cota <cota@braap.org>
|
|
*
|
|
* License: GNU GPL, version 2 or later.
|
|
* See the COPYING file in the top-level directory.
|
|
*/
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/processor.h"
|
|
#include "qemu/atomic.h"
|
|
#include "qemu/qht.h"
|
|
#include "qemu/rcu.h"
|
|
#include "exec/tb-hash-xx.h"
|
|
|
|
struct thread_stats {
|
|
size_t rd;
|
|
size_t not_rd;
|
|
size_t in;
|
|
size_t not_in;
|
|
size_t rm;
|
|
size_t not_rm;
|
|
size_t rz;
|
|
size_t not_rz;
|
|
};
|
|
|
|
struct thread_info {
|
|
void (*func)(struct thread_info *);
|
|
struct thread_stats stats;
|
|
uint64_t r;
|
|
bool write_op; /* writes alternate between insertions and removals */
|
|
bool resize_down;
|
|
} QEMU_ALIGNED(64); /* avoid false sharing among threads */
|
|
|
|
static struct qht ht;
|
|
static QemuThread *rw_threads;
|
|
|
|
#define DEFAULT_RANGE (4096)
|
|
#define DEFAULT_QHT_N_ELEMS DEFAULT_RANGE
|
|
|
|
static unsigned int duration = 1;
|
|
static unsigned int n_rw_threads = 1;
|
|
static unsigned long lookup_range = DEFAULT_RANGE;
|
|
static unsigned long update_range = DEFAULT_RANGE;
|
|
static size_t init_range = DEFAULT_RANGE;
|
|
static size_t init_size = DEFAULT_RANGE;
|
|
static size_t n_ready_threads;
|
|
static long populate_offset;
|
|
static long *keys;
|
|
|
|
static size_t resize_min;
|
|
static size_t resize_max;
|
|
static struct thread_info *rz_info;
|
|
static unsigned long resize_delay = 1000;
|
|
static double resize_rate; /* 0.0 to 1.0 */
|
|
static unsigned int n_rz_threads = 1;
|
|
static QemuThread *rz_threads;
|
|
|
|
static double update_rate; /* 0.0 to 1.0 */
|
|
static uint64_t update_threshold;
|
|
static uint64_t resize_threshold;
|
|
|
|
static size_t qht_n_elems = DEFAULT_QHT_N_ELEMS;
|
|
static int qht_mode;
|
|
|
|
static bool test_start;
|
|
static bool test_stop;
|
|
|
|
static struct thread_info *rw_info;
|
|
|
|
static const char commands_string[] =
|
|
" -d = duration, in seconds\n"
|
|
" -n = number of threads\n"
|
|
"\n"
|
|
" -o = offset at which keys start\n"
|
|
"\n"
|
|
" -g = set -s,-k,-K,-l,-r to the same value\n"
|
|
" -s = initial size hint\n"
|
|
" -k = initial number of keys\n"
|
|
" -K = initial range of keys (will be rounded up to pow2)\n"
|
|
" -l = lookup range of keys (will be rounded up to pow2)\n"
|
|
" -r = update range of keys (will be rounded up to pow2)\n"
|
|
"\n"
|
|
" -u = update rate (0.0 to 100.0), 50/50 split of insertions/removals\n"
|
|
"\n"
|
|
" -R = enable auto-resize\n"
|
|
" -S = resize rate (0.0 to 100.0)\n"
|
|
" -D = delay (in us) between potential resizes\n"
|
|
" -N = number of resize threads";
|
|
|
|
static void usage_complete(int argc, char *argv[])
|
|
{
|
|
fprintf(stderr, "Usage: %s [options]\n", argv[0]);
|
|
fprintf(stderr, "options:\n%s\n", commands_string);
|
|
exit(-1);
|
|
}
|
|
|
|
static bool is_equal(const void *obj, const void *userp)
|
|
{
|
|
const long *a = obj;
|
|
const long *b = userp;
|
|
|
|
return *a == *b;
|
|
}
|
|
|
|
static inline uint32_t h(unsigned long v)
|
|
{
|
|
return tb_hash_func6(v, 0, 0, 0);
|
|
}
|
|
|
|
/*
|
|
* From: https://en.wikipedia.org/wiki/Xorshift
|
|
* This is faster than rand_r(), and gives us a wider range (RAND_MAX is only
|
|
* guaranteed to be >= INT_MAX).
|
|
*/
|
|
static uint64_t xorshift64star(uint64_t x)
|
|
{
|
|
x ^= x >> 12; /* a */
|
|
x ^= x << 25; /* b */
|
|
x ^= x >> 27; /* c */
|
|
return x * UINT64_C(2685821657736338717);
|
|
}
|
|
|
|
static void do_rz(struct thread_info *info)
|
|
{
|
|
struct thread_stats *stats = &info->stats;
|
|
|
|
if (info->r < resize_threshold) {
|
|
size_t size = info->resize_down ? resize_min : resize_max;
|
|
bool resized;
|
|
|
|
resized = qht_resize(&ht, size);
|
|
info->resize_down = !info->resize_down;
|
|
|
|
if (resized) {
|
|
stats->rz++;
|
|
} else {
|
|
stats->not_rz++;
|
|
}
|
|
}
|
|
g_usleep(resize_delay);
|
|
}
|
|
|
|
static void do_rw(struct thread_info *info)
|
|
{
|
|
struct thread_stats *stats = &info->stats;
|
|
uint32_t hash;
|
|
long *p;
|
|
|
|
if (info->r >= update_threshold) {
|
|
bool read;
|
|
|
|
p = &keys[info->r & (lookup_range - 1)];
|
|
hash = h(*p);
|
|
read = qht_lookup(&ht, is_equal, p, hash);
|
|
if (read) {
|
|
stats->rd++;
|
|
} else {
|
|
stats->not_rd++;
|
|
}
|
|
} else {
|
|
p = &keys[info->r & (update_range - 1)];
|
|
hash = h(*p);
|
|
if (info->write_op) {
|
|
bool written = false;
|
|
|
|
if (qht_lookup(&ht, is_equal, p, hash) == NULL) {
|
|
written = qht_insert(&ht, p, hash);
|
|
}
|
|
if (written) {
|
|
stats->in++;
|
|
} else {
|
|
stats->not_in++;
|
|
}
|
|
} else {
|
|
bool removed = false;
|
|
|
|
if (qht_lookup(&ht, is_equal, p, hash)) {
|
|
removed = qht_remove(&ht, p, hash);
|
|
}
|
|
if (removed) {
|
|
stats->rm++;
|
|
} else {
|
|
stats->not_rm++;
|
|
}
|
|
}
|
|
info->write_op = !info->write_op;
|
|
}
|
|
}
|
|
|
|
static void *thread_func(void *p)
|
|
{
|
|
struct thread_info *info = p;
|
|
|
|
rcu_register_thread();
|
|
|
|
atomic_inc(&n_ready_threads);
|
|
while (!atomic_read(&test_start)) {
|
|
cpu_relax();
|
|
}
|
|
|
|
rcu_read_lock();
|
|
while (!atomic_read(&test_stop)) {
|
|
info->r = xorshift64star(info->r);
|
|
info->func(info);
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
rcu_unregister_thread();
|
|
return NULL;
|
|
}
|
|
|
|
/* sets everything except info->func */
|
|
static void prepare_thread_info(struct thread_info *info, int i)
|
|
{
|
|
/* seed for the RNG; each thread should have a different one */
|
|
info->r = (i + 1) ^ time(NULL);
|
|
/* the first update will be a write */
|
|
info->write_op = true;
|
|
/* the first resize will be down */
|
|
info->resize_down = true;
|
|
|
|
memset(&info->stats, 0, sizeof(info->stats));
|
|
}
|
|
|
|
static void
|
|
th_create_n(QemuThread **threads, struct thread_info **infos, const char *name,
|
|
void (*func)(struct thread_info *), int offset, int n)
|
|
{
|
|
struct thread_info *info;
|
|
QemuThread *th;
|
|
int i;
|
|
|
|
th = g_malloc(sizeof(*th) * n);
|
|
*threads = th;
|
|
|
|
info = qemu_memalign(64, sizeof(*info) * n);
|
|
*infos = info;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
prepare_thread_info(&info[i], offset + i);
|
|
info[i].func = func;
|
|
qemu_thread_create(&th[i], name, thread_func, &info[i],
|
|
QEMU_THREAD_JOINABLE);
|
|
}
|
|
}
|
|
|
|
static void create_threads(void)
|
|
{
|
|
th_create_n(&rw_threads, &rw_info, "rw", do_rw, 0, n_rw_threads);
|
|
th_create_n(&rz_threads, &rz_info, "rz", do_rz, n_rw_threads, n_rz_threads);
|
|
}
|
|
|
|
static void pr_params(void)
|
|
{
|
|
printf("Parameters:\n");
|
|
printf(" duration: %d s\n", duration);
|
|
printf(" # of threads: %u\n", n_rw_threads);
|
|
printf(" initial # of keys: %zu\n", init_size);
|
|
printf(" initial size hint: %zu\n", qht_n_elems);
|
|
printf(" auto-resize: %s\n",
|
|
qht_mode & QHT_MODE_AUTO_RESIZE ? "on" : "off");
|
|
if (resize_rate) {
|
|
printf(" resize_rate: %f%%\n", resize_rate * 100.0);
|
|
printf(" resize range: %zu-%zu\n", resize_min, resize_max);
|
|
printf(" # resize threads %u\n", n_rz_threads);
|
|
}
|
|
printf(" update rate: %f%%\n", update_rate * 100.0);
|
|
printf(" offset: %ld\n", populate_offset);
|
|
printf(" initial key range: %zu\n", init_range);
|
|
printf(" lookup range: %lu\n", lookup_range);
|
|
printf(" update range: %lu\n", update_range);
|
|
}
|
|
|
|
static void do_threshold(double rate, uint64_t *threshold)
|
|
{
|
|
if (rate == 1.0) {
|
|
*threshold = UINT64_MAX;
|
|
} else {
|
|
*threshold = rate * UINT64_MAX;
|
|
}
|
|
}
|
|
|
|
static void htable_init(void)
|
|
{
|
|
unsigned long n = MAX(init_range, update_range);
|
|
uint64_t r = time(NULL);
|
|
size_t retries = 0;
|
|
size_t i;
|
|
|
|
/* avoid allocating memory later by allocating all the keys now */
|
|
keys = g_malloc(sizeof(*keys) * n);
|
|
for (i = 0; i < n; i++) {
|
|
keys[i] = populate_offset + i;
|
|
}
|
|
|
|
/* some sanity checks */
|
|
g_assert_cmpuint(lookup_range, <=, n);
|
|
|
|
/* compute thresholds */
|
|
do_threshold(update_rate, &update_threshold);
|
|
do_threshold(resize_rate, &resize_threshold);
|
|
|
|
if (resize_rate) {
|
|
resize_min = n / 2;
|
|
resize_max = n;
|
|
assert(resize_min < resize_max);
|
|
} else {
|
|
n_rz_threads = 0;
|
|
}
|
|
|
|
/* initialize the hash table */
|
|
qht_init(&ht, qht_n_elems, qht_mode);
|
|
assert(init_size <= init_range);
|
|
|
|
pr_params();
|
|
|
|
fprintf(stderr, "Initialization: populating %zu items...", init_size);
|
|
for (i = 0; i < init_size; i++) {
|
|
for (;;) {
|
|
uint32_t hash;
|
|
long *p;
|
|
|
|
r = xorshift64star(r);
|
|
p = &keys[r & (init_range - 1)];
|
|
hash = h(*p);
|
|
if (qht_insert(&ht, p, hash)) {
|
|
break;
|
|
}
|
|
retries++;
|
|
}
|
|
}
|
|
fprintf(stderr, " populated after %zu retries\n", retries);
|
|
}
|
|
|
|
static void add_stats(struct thread_stats *s, struct thread_info *info, int n)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
struct thread_stats *stats = &info[i].stats;
|
|
|
|
s->rd += stats->rd;
|
|
s->not_rd += stats->not_rd;
|
|
|
|
s->in += stats->in;
|
|
s->not_in += stats->not_in;
|
|
|
|
s->rm += stats->rm;
|
|
s->not_rm += stats->not_rm;
|
|
|
|
s->rz += stats->rz;
|
|
s->not_rz += stats->not_rz;
|
|
}
|
|
}
|
|
|
|
static void pr_stats(void)
|
|
{
|
|
struct thread_stats s = {};
|
|
double tx;
|
|
|
|
add_stats(&s, rw_info, n_rw_threads);
|
|
add_stats(&s, rz_info, n_rz_threads);
|
|
|
|
printf("Results:\n");
|
|
|
|
if (resize_rate) {
|
|
printf(" Resizes: %zu (%.2f%% of %zu)\n",
|
|
s.rz, (double)s.rz / (s.rz + s.not_rz) * 100, s.rz + s.not_rz);
|
|
}
|
|
|
|
printf(" Read: %.2f M (%.2f%% of %.2fM)\n",
|
|
(double)s.rd / 1e6,
|
|
(double)s.rd / (s.rd + s.not_rd) * 100,
|
|
(double)(s.rd + s.not_rd) / 1e6);
|
|
printf(" Inserted: %.2f M (%.2f%% of %.2fM)\n",
|
|
(double)s.in / 1e6,
|
|
(double)s.in / (s.in + s.not_in) * 100,
|
|
(double)(s.in + s.not_in) / 1e6);
|
|
printf(" Removed: %.2f M (%.2f%% of %.2fM)\n",
|
|
(double)s.rm / 1e6,
|
|
(double)s.rm / (s.rm + s.not_rm) * 100,
|
|
(double)(s.rm + s.not_rm) / 1e6);
|
|
|
|
tx = (s.rd + s.not_rd + s.in + s.not_in + s.rm + s.not_rm) / 1e6 / duration;
|
|
printf(" Throughput: %.2f MT/s\n", tx);
|
|
printf(" Throughput/thread: %.2f MT/s/thread\n", tx / n_rw_threads);
|
|
}
|
|
|
|
static void run_test(void)
|
|
{
|
|
unsigned int remaining;
|
|
int i;
|
|
|
|
while (atomic_read(&n_ready_threads) != n_rw_threads + n_rz_threads) {
|
|
cpu_relax();
|
|
}
|
|
atomic_set(&test_start, true);
|
|
do {
|
|
remaining = sleep(duration);
|
|
} while (remaining);
|
|
atomic_set(&test_stop, true);
|
|
|
|
for (i = 0; i < n_rw_threads; i++) {
|
|
qemu_thread_join(&rw_threads[i]);
|
|
}
|
|
for (i = 0; i < n_rz_threads; i++) {
|
|
qemu_thread_join(&rz_threads[i]);
|
|
}
|
|
}
|
|
|
|
static void parse_args(int argc, char *argv[])
|
|
{
|
|
int c;
|
|
|
|
for (;;) {
|
|
c = getopt(argc, argv, "d:D:g:k:K:l:hn:N:o:r:Rs:S:u:");
|
|
if (c < 0) {
|
|
break;
|
|
}
|
|
switch (c) {
|
|
case 'd':
|
|
duration = atoi(optarg);
|
|
break;
|
|
case 'D':
|
|
resize_delay = atol(optarg);
|
|
break;
|
|
case 'g':
|
|
init_range = pow2ceil(atol(optarg));
|
|
lookup_range = pow2ceil(atol(optarg));
|
|
update_range = pow2ceil(atol(optarg));
|
|
qht_n_elems = atol(optarg);
|
|
init_size = atol(optarg);
|
|
break;
|
|
case 'h':
|
|
usage_complete(argc, argv);
|
|
exit(0);
|
|
case 'k':
|
|
init_size = atol(optarg);
|
|
break;
|
|
case 'K':
|
|
init_range = pow2ceil(atol(optarg));
|
|
break;
|
|
case 'l':
|
|
lookup_range = pow2ceil(atol(optarg));
|
|
break;
|
|
case 'n':
|
|
n_rw_threads = atoi(optarg);
|
|
break;
|
|
case 'N':
|
|
n_rz_threads = atoi(optarg);
|
|
break;
|
|
case 'o':
|
|
populate_offset = atol(optarg);
|
|
break;
|
|
case 'r':
|
|
update_range = pow2ceil(atol(optarg));
|
|
break;
|
|
case 'R':
|
|
qht_mode |= QHT_MODE_AUTO_RESIZE;
|
|
break;
|
|
case 's':
|
|
qht_n_elems = atol(optarg);
|
|
break;
|
|
case 'S':
|
|
resize_rate = atof(optarg) / 100.0;
|
|
if (resize_rate > 1.0) {
|
|
resize_rate = 1.0;
|
|
}
|
|
break;
|
|
case 'u':
|
|
update_rate = atof(optarg) / 100.0;
|
|
if (update_rate > 1.0) {
|
|
update_rate = 1.0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
parse_args(argc, argv);
|
|
htable_init();
|
|
create_threads();
|
|
run_test();
|
|
pr_stats();
|
|
return 0;
|
|
}
|