/* 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; }