qsp: QEMU's Synchronization Profiler

The goal of this module is to profile synchronization primitives (i.e.
mutexes, recursive mutexes and condition variables) so that scalability
issues can be quickly diagnosed.

Sync primitives are profiled by QSP based on the vaddr of the object accessed
as well as the call site (file:line_nr). That means the same object called
from two different call sites will be tracked in separate entries, which
might be reported together or separately (see subsequent commit on
call site coalescing).

Some perf numbers:

Host: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
Command: taskset -c 0 tests/atomic_add-bench -d 5 -m

- Before: 54.80 Mops/s
- After:  54.75 Mops/s

That is, a negligible slowdown due to the now indirect call to
qemu_mutex_lock. Note that using a branch instead of an indirect
call introduces a more severe slowdown (53.65 Mops/s, i.e. 2% slowdown).

Enabling the profiler (with -p, added in this series) is more interesting:

- No profiling: 54.75 Mops/s
- W/ profiling: 12.53 Mops/s

That is, a 4.36X slowdown.

We can break down this slowdown by removing the get_clock calls or
the entry lookup:

- No profiling:     54.75 Mops/s
- W/o get_clock:    25.37 Mops/s
- W/o entry lookup: 19.30 Mops/s
- W/ profiling:     12.53 Mops/s

Signed-off-by: Emilio G. Cota <cota@braap.org>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Emilio G. Cota
2018-08-23 18:46:25 +02:00
committed by Paolo Bonzini
parent c04649eeea
commit fe9959a275
9 changed files with 759 additions and 23 deletions
+1
View File
@@ -46,6 +46,7 @@ typedef bool (*qht_lookup_func_t)(const void *obj, const void *userp);
typedef void (*qht_iter_func_t)(struct qht *ht, void *p, uint32_t h, void *up);
#define QHT_MODE_AUTO_RESIZE 0x1 /* auto-resize when heavily loaded */
#define QHT_MODE_RAW_MUTEXES 0x2 /* bypass the profiler (QSP) */
/**
* qht_init - Initialize a QHT
+22
View File
@@ -0,0 +1,22 @@
/*
* qsp.c - QEMU Synchronization Profiler
*
* Copyright (C) 2018, Emilio G. Cota <cota@braap.org>
*
* License: GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
* Note: this header file can *only* be included from thread.h.
*/
#ifndef QEMU_QSP_H
#define QEMU_QSP_H
#include "qemu/fprintf-fn.h"
void qsp_report(FILE *f, fprintf_function cpu_fprintf, size_t max);
bool qsp_is_enabled(void);
void qsp_enable(void);
void qsp_disable(void);
#endif /* QEMU_QSP_H */
+2 -2
View File
@@ -6,8 +6,8 @@
typedef QemuMutex QemuRecMutex;
#define qemu_rec_mutex_destroy qemu_mutex_destroy
#define qemu_rec_mutex_lock qemu_mutex_lock
#define qemu_rec_mutex_trylock qemu_mutex_trylock
#define qemu_rec_mutex_lock_impl qemu_mutex_lock_impl
#define qemu_rec_mutex_trylock_impl qemu_mutex_trylock_impl
#define qemu_rec_mutex_unlock qemu_mutex_unlock
struct QemuMutex {
+3 -2
View File
@@ -19,8 +19,9 @@ struct QemuRecMutex {
};
void qemu_rec_mutex_destroy(QemuRecMutex *mutex);
void qemu_rec_mutex_lock(QemuRecMutex *mutex);
int qemu_rec_mutex_trylock(QemuRecMutex *mutex);
void qemu_rec_mutex_lock_impl(QemuRecMutex *mutex, const char *file, int line);
int qemu_rec_mutex_trylock_impl(QemuRecMutex *mutex, const char *file,
int line);
void qemu_rec_mutex_unlock(QemuRecMutex *mutex);
struct QemuCond {
+58 -7
View File
@@ -16,6 +16,9 @@ typedef struct QemuThread QemuThread;
#include "qemu/thread-posix.h"
#endif
/* include QSP header once QemuMutex, QemuCond etc. are defined */
#include "qemu/qsp.h"
#define QEMU_THREAD_JOINABLE 0
#define QEMU_THREAD_DETACHED 1
@@ -25,10 +28,51 @@ int qemu_mutex_trylock_impl(QemuMutex *mutex, const char *file, const int line);
void qemu_mutex_lock_impl(QemuMutex *mutex, const char *file, const int line);
void qemu_mutex_unlock_impl(QemuMutex *mutex, const char *file, const int line);
#define qemu_mutex_lock(mutex) \
qemu_mutex_lock_impl(mutex, __FILE__, __LINE__)
#define qemu_mutex_trylock(mutex) \
qemu_mutex_trylock_impl(mutex, __FILE__, __LINE__)
typedef void (*QemuMutexLockFunc)(QemuMutex *m, const char *f, int l);
typedef int (*QemuMutexTrylockFunc)(QemuMutex *m, const char *f, int l);
typedef void (*QemuRecMutexLockFunc)(QemuRecMutex *m, const char *f, int l);
typedef int (*QemuRecMutexTrylockFunc)(QemuRecMutex *m, const char *f, int l);
typedef void (*QemuCondWaitFunc)(QemuCond *c, QemuMutex *m, const char *f,
int l);
extern QemuMutexLockFunc qemu_mutex_lock_func;
extern QemuMutexTrylockFunc qemu_mutex_trylock_func;
extern QemuRecMutexLockFunc qemu_rec_mutex_lock_func;
extern QemuRecMutexTrylockFunc qemu_rec_mutex_trylock_func;
extern QemuCondWaitFunc qemu_cond_wait_func;
/* convenience macros to bypass the profiler */
#define qemu_mutex_lock__raw(m) \
qemu_mutex_lock_impl(m, __FILE__, __LINE__)
#define qemu_mutex_trylock__raw(m) \
qemu_mutex_trylock_impl(m, __FILE__, __LINE__)
#define qemu_mutex_lock(m) ({ \
QemuMutexLockFunc _f = atomic_read(&qemu_mutex_lock_func); \
_f(m, __FILE__, __LINE__); \
})
#define qemu_mutex_trylock(m) ({ \
QemuMutexTrylockFunc _f = atomic_read(&qemu_mutex_trylock_func); \
_f(m, __FILE__, __LINE__); \
})
#define qemu_rec_mutex_lock(m) ({ \
QemuRecMutexLockFunc _f = atomic_read(&qemu_rec_mutex_lock_func); \
_f(m, __FILE__, __LINE__); \
})
#define qemu_rec_mutex_trylock(m) ({ \
QemuRecMutexTrylockFunc _f; \
_f = atomic_read(&qemu_rec_mutex_trylock_func); \
_f(m, __FILE__, __LINE__); \
})
#define qemu_cond_wait(c, m) ({ \
QemuCondWaitFunc _f = atomic_read(&qemu_cond_wait_func); \
_f(c, m, __FILE__, __LINE__); \
})
#define qemu_mutex_unlock(mutex) \
qemu_mutex_unlock_impl(mutex, __FILE__, __LINE__)
@@ -47,6 +91,16 @@ static inline void (qemu_mutex_unlock)(QemuMutex *mutex)
qemu_mutex_unlock(mutex);
}
static inline void (qemu_rec_mutex_lock)(QemuRecMutex *mutex)
{
qemu_rec_mutex_lock(mutex);
}
static inline int (qemu_rec_mutex_trylock)(QemuRecMutex *mutex)
{
return qemu_rec_mutex_trylock(mutex);
}
/* Prototypes for other functions are in thread-posix.h/thread-win32.h. */
void qemu_rec_mutex_init(QemuRecMutex *mutex);
@@ -63,9 +117,6 @@ void qemu_cond_broadcast(QemuCond *cond);
void qemu_cond_wait_impl(QemuCond *cond, QemuMutex *mutex,
const char *file, const int line);
#define qemu_cond_wait(cond, mutex) \
qemu_cond_wait_impl(cond, mutex, __FILE__, __LINE__)
static inline void (qemu_cond_wait)(QemuCond *cond, QemuMutex *mutex)
{
qemu_cond_wait(cond, mutex);
+1
View File
@@ -44,6 +44,7 @@ util-obj-y += log.o
util-obj-y += pagesize.o
util-obj-y += qdist.o
util-obj-y += qht.o
util-obj-y += qsp.o
util-obj-y += range.o
util-obj-y += stats64.o
util-obj-y += systemd.o
+2 -2
View File
@@ -97,13 +97,13 @@ void qemu_rec_mutex_destroy(QemuRecMutex *mutex)
DeleteCriticalSection(&mutex->lock);
}
void qemu_rec_mutex_lock(QemuRecMutex *mutex)
void qemu_rec_mutex_lock_impl(QemuRecMutex *mutex, const char *file, int line)
{
assert(mutex->initialized);
EnterCriticalSection(&mutex->lock);
}
int qemu_rec_mutex_trylock(QemuRecMutex *mutex)
int qemu_rec_mutex_trylock_impl(QemuRecMutex *mutex, const char *file, int line)
{
assert(mutex->initialized);
return !TryEnterCriticalSection(&mutex->lock);
+37 -10
View File
@@ -89,6 +89,33 @@
#define QHT_BUCKET_ENTRIES 4
#endif
/*
* Do _not_ use qemu_mutex_[try]lock directly! Use these macros, otherwise
* the profiler (QSP) will deadlock.
*/
static inline void qht_lock(struct qht *ht)
{
if (ht->mode & QHT_MODE_RAW_MUTEXES) {
qemu_mutex_lock__raw(&ht->lock);
} else {
qemu_mutex_lock(&ht->lock);
}
}
static inline int qht_trylock(struct qht *ht)
{
if (ht->mode & QHT_MODE_RAW_MUTEXES) {
return qemu_mutex_trylock__raw(&(ht)->lock);
}
return qemu_mutex_trylock(&(ht)->lock);
}
/* this inline is not really necessary, but it helps keep code consistent */
static inline void qht_unlock(struct qht *ht)
{
qemu_mutex_unlock(&ht->lock);
}
/*
* Note: reading partially-updated pointers in @pointers could lead to
* segfaults. We thus access them with atomic_read/set; this guarantees
@@ -254,10 +281,10 @@ void qht_map_lock_buckets__no_stale(struct qht *ht, struct qht_map **pmap)
qht_map_unlock_buckets(map);
/* we raced with a resize; acquire ht->lock to see the updated ht->map */
qemu_mutex_lock(&ht->lock);
qht_lock(ht);
map = ht->map;
qht_map_lock_buckets(map);
qemu_mutex_unlock(&ht->lock);
qht_unlock(ht);
*pmap = map;
return;
}
@@ -288,11 +315,11 @@ struct qht_bucket *qht_bucket_lock__no_stale(struct qht *ht, uint32_t hash,
qemu_spin_unlock(&b->lock);
/* we raced with a resize; acquire ht->lock to see the updated ht->map */
qemu_mutex_lock(&ht->lock);
qht_lock(ht);
map = ht->map;
b = qht_map_to_bucket(map, hash);
qemu_spin_lock(&b->lock);
qemu_mutex_unlock(&ht->lock);
qht_unlock(ht);
*pmap = map;
return b;
}
@@ -430,13 +457,13 @@ bool qht_reset_size(struct qht *ht, size_t n_elems)
n_buckets = qht_elems_to_buckets(n_elems);
qemu_mutex_lock(&ht->lock);
qht_lock(ht);
map = ht->map;
if (n_buckets != map->n_buckets) {
new = qht_map_create(n_buckets);
}
qht_do_resize_and_reset(ht, new);
qemu_mutex_unlock(&ht->lock);
qht_unlock(ht);
return !!new;
}
@@ -565,7 +592,7 @@ static __attribute__((noinline)) void qht_grow_maybe(struct qht *ht)
* If the lock is taken it probably means there's an ongoing resize,
* so bail out.
*/
if (qemu_mutex_trylock(&ht->lock)) {
if (qht_trylock(ht)) {
return;
}
map = ht->map;
@@ -575,7 +602,7 @@ static __attribute__((noinline)) void qht_grow_maybe(struct qht *ht)
qht_do_resize(ht, new);
}
qemu_mutex_unlock(&ht->lock);
qht_unlock(ht);
}
bool qht_insert(struct qht *ht, void *p, uint32_t hash, void **existing)
@@ -788,7 +815,7 @@ bool qht_resize(struct qht *ht, size_t n_elems)
size_t n_buckets = qht_elems_to_buckets(n_elems);
size_t ret = false;
qemu_mutex_lock(&ht->lock);
qht_lock(ht);
if (n_buckets != ht->map->n_buckets) {
struct qht_map *new;
@@ -796,7 +823,7 @@ bool qht_resize(struct qht *ht, size_t n_elems)
qht_do_resize(ht, new);
ret = true;
}
qemu_mutex_unlock(&ht->lock);
qht_unlock(ht);
return ret;
}
+633
View File
File diff suppressed because it is too large Load Diff