287 lines
7.0 KiB
C
287 lines
7.0 KiB
C
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||
|
#include "qemu/osdep.h"
|
||
|
#include "qemu/qtree.h"
|
||
|
#include "qemu/timer.h"
|
||
|
|
||
|
enum tree_op {
|
||
|
OP_LOOKUP,
|
||
|
OP_INSERT,
|
||
|
OP_REMOVE,
|
||
|
OP_REMOVE_ALL,
|
||
|
OP_TRAVERSE,
|
||
|
};
|
||
|
|
||
|
struct benchmark {
|
||
|
const char * const name;
|
||
|
enum tree_op op;
|
||
|
bool fill_on_init;
|
||
|
};
|
||
|
|
||
|
enum impl_type {
|
||
|
IMPL_GTREE,
|
||
|
IMPL_QTREE,
|
||
|
};
|
||
|
|
||
|
struct tree_implementation {
|
||
|
const char * const name;
|
||
|
enum impl_type type;
|
||
|
};
|
||
|
|
||
|
static const struct benchmark benchmarks[] = {
|
||
|
{
|
||
|
.name = "Lookup",
|
||
|
.op = OP_LOOKUP,
|
||
|
.fill_on_init = true,
|
||
|
},
|
||
|
{
|
||
|
.name = "Insert",
|
||
|
.op = OP_INSERT,
|
||
|
.fill_on_init = false,
|
||
|
},
|
||
|
{
|
||
|
.name = "Remove",
|
||
|
.op = OP_REMOVE,
|
||
|
.fill_on_init = true,
|
||
|
},
|
||
|
{
|
||
|
.name = "RemoveAll",
|
||
|
.op = OP_REMOVE_ALL,
|
||
|
.fill_on_init = true,
|
||
|
},
|
||
|
{
|
||
|
.name = "Traverse",
|
||
|
.op = OP_TRAVERSE,
|
||
|
.fill_on_init = true,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static const struct tree_implementation impls[] = {
|
||
|
{
|
||
|
.name = "GTree",
|
||
|
.type = IMPL_GTREE,
|
||
|
},
|
||
|
{
|
||
|
.name = "QTree",
|
||
|
.type = IMPL_QTREE,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int compare_func(const void *ap, const void *bp)
|
||
|
{
|
||
|
const size_t *a = ap;
|
||
|
const size_t *b = bp;
|
||
|
|
||
|
return *a - *b;
|
||
|
}
|
||
|
|
||
|
static void init_empty_tree_and_keys(enum impl_type impl,
|
||
|
void **ret_tree, size_t **ret_keys,
|
||
|
size_t n_elems)
|
||
|
{
|
||
|
size_t *keys = g_malloc_n(n_elems, sizeof(*keys));
|
||
|
for (size_t i = 0; i < n_elems; i++) {
|
||
|
keys[i] = i;
|
||
|
}
|
||
|
|
||
|
void *tree;
|
||
|
switch (impl) {
|
||
|
case IMPL_GTREE:
|
||
|
tree = g_tree_new(compare_func);
|
||
|
break;
|
||
|
case IMPL_QTREE:
|
||
|
tree = q_tree_new(compare_func);
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
|
||
|
*ret_tree = tree;
|
||
|
*ret_keys = keys;
|
||
|
}
|
||
|
|
||
|
static gboolean traverse_func(gpointer key, gpointer value, gpointer data)
|
||
|
{
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static inline void remove_all(void *tree, enum impl_type impl)
|
||
|
{
|
||
|
switch (impl) {
|
||
|
case IMPL_GTREE:
|
||
|
g_tree_destroy(tree);
|
||
|
break;
|
||
|
case IMPL_QTREE:
|
||
|
q_tree_destroy(tree);
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int64_t run_benchmark(const struct benchmark *bench,
|
||
|
enum impl_type impl,
|
||
|
size_t n_elems)
|
||
|
{
|
||
|
void *tree;
|
||
|
size_t *keys;
|
||
|
|
||
|
init_empty_tree_and_keys(impl, &tree, &keys, n_elems);
|
||
|
if (bench->fill_on_init) {
|
||
|
for (size_t i = 0; i < n_elems; i++) {
|
||
|
switch (impl) {
|
||
|
case IMPL_GTREE:
|
||
|
g_tree_insert(tree, &keys[i], &keys[i]);
|
||
|
break;
|
||
|
case IMPL_QTREE:
|
||
|
q_tree_insert(tree, &keys[i], &keys[i]);
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int64_t start_ns = get_clock();
|
||
|
switch (bench->op) {
|
||
|
case OP_LOOKUP:
|
||
|
for (size_t i = 0; i < n_elems; i++) {
|
||
|
void *value;
|
||
|
switch (impl) {
|
||
|
case IMPL_GTREE:
|
||
|
value = g_tree_lookup(tree, &keys[i]);
|
||
|
break;
|
||
|
case IMPL_QTREE:
|
||
|
value = q_tree_lookup(tree, &keys[i]);
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
(void)value;
|
||
|
}
|
||
|
break;
|
||
|
case OP_INSERT:
|
||
|
for (size_t i = 0; i < n_elems; i++) {
|
||
|
switch (impl) {
|
||
|
case IMPL_GTREE:
|
||
|
g_tree_insert(tree, &keys[i], &keys[i]);
|
||
|
break;
|
||
|
case IMPL_QTREE:
|
||
|
q_tree_insert(tree, &keys[i], &keys[i]);
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case OP_REMOVE:
|
||
|
for (size_t i = 0; i < n_elems; i++) {
|
||
|
switch (impl) {
|
||
|
case IMPL_GTREE:
|
||
|
g_tree_remove(tree, &keys[i]);
|
||
|
break;
|
||
|
case IMPL_QTREE:
|
||
|
q_tree_remove(tree, &keys[i]);
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case OP_REMOVE_ALL:
|
||
|
remove_all(tree, impl);
|
||
|
break;
|
||
|
case OP_TRAVERSE:
|
||
|
switch (impl) {
|
||
|
case IMPL_GTREE:
|
||
|
g_tree_foreach(tree, traverse_func, NULL);
|
||
|
break;
|
||
|
case IMPL_QTREE:
|
||
|
q_tree_foreach(tree, traverse_func, NULL);
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached();
|
||
|
}
|
||
|
int64_t ns = get_clock() - start_ns;
|
||
|
|
||
|
if (bench->op != OP_REMOVE_ALL) {
|
||
|
remove_all(tree, impl);
|
||
|
}
|
||
|
g_free(keys);
|
||
|
|
||
|
return ns;
|
||
|
}
|
||
|
|
||
|
int main(int argc, char *argv[])
|
||
|
{
|
||
|
size_t sizes[] = {
|
||
|
32,
|
||
|
1024,
|
||
|
1024 * 4,
|
||
|
1024 * 128,
|
||
|
1024 * 1024,
|
||
|
};
|
||
|
|
||
|
double res[ARRAY_SIZE(benchmarks)][ARRAY_SIZE(impls)][ARRAY_SIZE(sizes)];
|
||
|
for (int i = 0; i < ARRAY_SIZE(sizes); i++) {
|
||
|
size_t size = sizes[i];
|
||
|
for (int j = 0; j < ARRAY_SIZE(impls); j++) {
|
||
|
const struct tree_implementation *impl = &impls[j];
|
||
|
for (int k = 0; k < ARRAY_SIZE(benchmarks); k++) {
|
||
|
const struct benchmark *bench = &benchmarks[k];
|
||
|
|
||
|
/* warm-up run */
|
||
|
run_benchmark(bench, impl->type, size);
|
||
|
|
||
|
int64_t total_ns = 0;
|
||
|
int64_t n_runs = 0;
|
||
|
while (total_ns < 2e8 || n_runs < 5) {
|
||
|
total_ns += run_benchmark(bench, impl->type, size);
|
||
|
n_runs++;
|
||
|
}
|
||
|
double ns_per_run = (double)total_ns / n_runs;
|
||
|
|
||
|
/* Throughput, in Mops/s */
|
||
|
res[k][j][i] = size / ns_per_run * 1e3;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
printf("# Results' breakdown: Tree, Op and #Elements. Units: Mops/s\n");
|
||
|
printf("%5s %10s ", "Tree", "Op");
|
||
|
for (int i = 0; i < ARRAY_SIZE(sizes); i++) {
|
||
|
printf("%7zu ", sizes[i]);
|
||
|
}
|
||
|
printf("\n");
|
||
|
char separator[97];
|
||
|
for (int i = 0; i < ARRAY_SIZE(separator) - 1; i++) {
|
||
|
separator[i] = '-';
|
||
|
}
|
||
|
separator[ARRAY_SIZE(separator) - 1] = '\0';
|
||
|
printf("%s\n", separator);
|
||
|
for (int i = 0; i < ARRAY_SIZE(benchmarks); i++) {
|
||
|
for (int j = 0; j < ARRAY_SIZE(impls); j++) {
|
||
|
printf("%5s %10s ", impls[j].name, benchmarks[i].name);
|
||
|
for (int k = 0; k < ARRAY_SIZE(sizes); k++) {
|
||
|
printf("%7.2f ", res[i][j][k]);
|
||
|
if (j == 0) {
|
||
|
printf(" ");
|
||
|
} else {
|
||
|
if (res[i][0][k] != 0) {
|
||
|
double speedup = res[i][j][k] / res[i][0][k];
|
||
|
printf("(%4.2fx) ", speedup);
|
||
|
} else {
|
||
|
printf("( ) ");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
printf("\n");
|
||
|
}
|
||
|
}
|
||
|
printf("%s\n", separator);
|
||
|
return 0;
|
||
|
}
|