util: import GTree as QTree

The only reason to add this implementation is to control the memory allocator
used. Some users (e.g. TCG) cannot work reliably in multi-threaded
environments (e.g. forking in user-mode) with GTree's allocator, GSlice.
See https://gitlab.com/qemu-project/qemu/-/issues/285 for details.

Importing GTree is a temporary workaround until GTree migrates away
from GSlice.

This implementation is identical to that in glib v2.75.0, except that
we don't import recent additions to the API nor deprecated API calls,
none of which are used in QEMU.

I've imported tests from glib and added a benchmark just to
make sure that performance is similar. Note: it cannot be identical
because (1) we are not using GSlice, (2) we use different compilation flags
(e.g. -fPIC) and (3) we're linking statically.

$ cat /proc/cpuinfo| grep 'model name' | head -1
model name      : AMD Ryzen 7 PRO 5850U with Radeon Graphics
$ echo '0' | sudo tee /sys/devices/system/cpu/cpufreq/boost
$ tests/bench/qtree-bench

 Tree         Op      32            1024            4096          131072         1048576
------------------------------------------------------------------------------------------------
GTree     Lookup   83.23           43.08           25.31           19.40           16.22
QTree     Lookup  113.42 (1.36x)   53.83 (1.25x)   28.38 (1.12x)   17.64 (0.91x)   13.04 (0.80x)
GTree     Insert   44.23           29.37           25.83           19.49           17.03
QTree     Insert   46.87 (1.06x)   25.62 (0.87x)   24.29 (0.94x)   16.83 (0.86x)   12.97 (0.76x)
GTree     Remove   53.27           35.15           31.43           24.64           16.70
QTree     Remove   57.32 (1.08x)   41.76 (1.19x)   38.37 (1.22x)   29.30 (1.19x)   15.07 (0.90x)
GTree  RemoveAll  135.44          127.52          126.72          120.11           64.34
QTree  RemoveAll  127.15 (0.94x)  110.37 (0.87x)  107.97 (0.85x)   97.13 (0.81x)   55.10 (0.86x)
GTree   Traverse  277.71          276.09          272.78          246.72           98.47
QTree   Traverse  370.33 (1.33x)  411.97 (1.49x)  400.23 (1.47x)  262.82 (1.07x)   78.52 (0.80x)
------------------------------------------------------------------------------------------------

As a sanity check, the same benchmark when Glib's version
is >= $glib_dropped_gslice_version (i.e. QTree == GTree):

 Tree         Op      32            1024            4096          131072         1048576
------------------------------------------------------------------------------------------------
GTree     Lookup   82.72           43.09           24.18           19.73           16.09
QTree     Lookup   81.82 (0.99x)   43.10 (1.00x)   24.20 (1.00x)   19.76 (1.00x)   16.26 (1.01x)
GTree     Insert   45.07           29.62           26.34           19.90           17.18
QTree     Insert   45.72 (1.01x)   29.60 (1.00x)   26.38 (1.00x)   19.71 (0.99x)   17.20 (1.00x)
GTree     Remove   54.48           35.36           31.77           24.97           16.95
QTree     Remove   54.46 (1.00x)   35.32 (1.00x)   31.77 (1.00x)   24.91 (1.00x)   17.15 (1.01x)
GTree  RemoveAll  140.68          127.36          125.43          121.45           68.20
QTree  RemoveAll  140.65 (1.00x)  127.64 (1.00x)  125.01 (1.00x)  121.73 (1.00x)   67.06 (0.98x)
GTree   Traverse  278.68          276.05          266.75          251.65          104.93
QTree   Traverse  278.31 (1.00x)  275.78 (1.00x)  266.42 (1.00x)  247.89 (0.99x)  104.58 (1.00x)
------------------------------------------------------------------------------------------------

Signed-off-by: Emilio Cota <cota@braap.org>
Message-Id: <20230205163758.416992-2-cota@braap.org>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Emilio Cota 2023-02-05 11:37:57 -05:00 committed by Richard Henderson
parent d37158bb24
commit e3feb2cc22
9 changed files with 2235 additions and 0 deletions

15
configure vendored
View File

@ -231,6 +231,7 @@ safe_stack=""
use_containers="yes" use_containers="yes"
gdb_bin=$(command -v "gdb-multiarch" || command -v "gdb") gdb_bin=$(command -v "gdb-multiarch" || command -v "gdb")
gdb_arches="" gdb_arches=""
glib_has_gslice="no"
if test -e "$source_path/.git" if test -e "$source_path/.git"
then then
@ -1494,6 +1495,17 @@ for i in $glib_modules; do
fi fi
done done
# Check whether glib has gslice, which we have to avoid for correctness.
# TODO: remove this check and the corresponding workaround (qtree) when
# the minimum supported glib is >= $glib_dropped_gslice_version.
glib_dropped_gslice_version=2.75.3
for i in $glib_modules; do
if ! $pkg_config --atleast-version=$glib_dropped_gslice_version $i; then
glib_has_gslice="yes"
break
fi
done
glib_bindir="$($pkg_config --variable=bindir glib-2.0)" glib_bindir="$($pkg_config --variable=bindir glib-2.0)"
if test -z "$glib_bindir" ; then if test -z "$glib_bindir" ; then
glib_bindir="$($pkg_config --variable=prefix glib-2.0)"/bin glib_bindir="$($pkg_config --variable=prefix glib-2.0)"/bin
@ -2420,6 +2432,9 @@ echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak
echo "GLIB_LIBS=$glib_libs" >> $config_host_mak echo "GLIB_LIBS=$glib_libs" >> $config_host_mak
echo "GLIB_BINDIR=$glib_bindir" >> $config_host_mak echo "GLIB_BINDIR=$glib_bindir" >> $config_host_mak
echo "GLIB_VERSION=$($pkg_config --modversion glib-2.0)" >> $config_host_mak echo "GLIB_VERSION=$($pkg_config --modversion glib-2.0)" >> $config_host_mak
if test "$glib_has_gslice" = "yes" ; then
echo "HAVE_GLIB_WITH_SLICE_ALLOCATOR=y" >> $config_host_mak
fi
echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak echo "QEMU_LDFLAGS=$QEMU_LDFLAGS" >> $config_host_mak
echo "EXESUF=$EXESUF" >> $config_host_mak echo "EXESUF=$EXESUF" >> $config_host_mak

201
include/qemu/qtree.h Normal file
View File

@ -0,0 +1,201 @@
/*
* GLIB - Library of useful routines for C programming
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GLib Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GLib Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GLib at ftp://ftp.gtk.org/pub/gtk/.
*/
/*
* QTree is a partial import of Glib's GTree. The parts excluded correspond
* to API calls either deprecated (e.g. g_tree_traverse) or recently added
* (e.g. g_tree_search_node, added in 2.68); neither have callers in QEMU.
*
* The reason for this import is to allow us to control the memory allocator
* used by the tree implementation. Until Glib 2.75.3, GTree uses Glib's
* slice allocator, which causes problems when forking in user-mode;
* see https://gitlab.com/qemu-project/qemu/-/issues/285 and glib's
* "45b5a6c1e gslice: Remove slice allocator and use malloc() instead".
*
* TODO: remove QTree when QEMU's minimum Glib version is >= 2.75.3.
*/
#ifndef QEMU_QTREE_H
#define QEMU_QTREE_H
#include "qemu/osdep.h"
#ifdef HAVE_GLIB_WITH_SLICE_ALLOCATOR
typedef struct _QTree QTree;
typedef struct _QTreeNode QTreeNode;
typedef gboolean (*QTraverseNodeFunc)(QTreeNode *node,
gpointer user_data);
/*
* Balanced binary trees
*/
QTree *q_tree_new(GCompareFunc key_compare_func);
QTree *q_tree_new_with_data(GCompareDataFunc key_compare_func,
gpointer key_compare_data);
QTree *q_tree_new_full(GCompareDataFunc key_compare_func,
gpointer key_compare_data,
GDestroyNotify key_destroy_func,
GDestroyNotify value_destroy_func);
QTree *q_tree_ref(QTree *tree);
void q_tree_unref(QTree *tree);
void q_tree_destroy(QTree *tree);
void q_tree_insert(QTree *tree,
gpointer key,
gpointer value);
void q_tree_replace(QTree *tree,
gpointer key,
gpointer value);
gboolean q_tree_remove(QTree *tree,
gconstpointer key);
gboolean q_tree_steal(QTree *tree,
gconstpointer key);
gpointer q_tree_lookup(QTree *tree,
gconstpointer key);
gboolean q_tree_lookup_extended(QTree *tree,
gconstpointer lookup_key,
gpointer *orig_key,
gpointer *value);
void q_tree_foreach(QTree *tree,
GTraverseFunc func,
gpointer user_data);
gpointer q_tree_search(QTree *tree,
GCompareFunc search_func,
gconstpointer user_data);
gint q_tree_height(QTree *tree);
gint q_tree_nnodes(QTree *tree);
#else /* !HAVE_GLIB_WITH_SLICE_ALLOCATOR */
typedef GTree QTree;
typedef GTreeNode QTreeNode;
typedef GTraverseNodeFunc QTraverseNodeFunc;
static inline QTree *q_tree_new(GCompareFunc key_compare_func)
{
return g_tree_new(key_compare_func);
}
static inline QTree *q_tree_new_with_data(GCompareDataFunc key_compare_func,
gpointer key_compare_data)
{
return g_tree_new_with_data(key_compare_func, key_compare_data);
}
static inline QTree *q_tree_new_full(GCompareDataFunc key_compare_func,
gpointer key_compare_data,
GDestroyNotify key_destroy_func,
GDestroyNotify value_destroy_func)
{
return g_tree_new_full(key_compare_func, key_compare_data,
key_destroy_func, value_destroy_func);
}
static inline QTree *q_tree_ref(QTree *tree)
{
return g_tree_ref(tree);
}
static inline void q_tree_unref(QTree *tree)
{
g_tree_unref(tree);
}
static inline void q_tree_destroy(QTree *tree)
{
g_tree_destroy(tree);
}
static inline void q_tree_insert(QTree *tree,
gpointer key,
gpointer value)
{
g_tree_insert(tree, key, value);
}
static inline void q_tree_replace(QTree *tree,
gpointer key,
gpointer value)
{
g_tree_replace(tree, key, value);
}
static inline gboolean q_tree_remove(QTree *tree,
gconstpointer key)
{
return g_tree_remove(tree, key);
}
static inline gboolean q_tree_steal(QTree *tree,
gconstpointer key)
{
return g_tree_steal(tree, key);
}
static inline gpointer q_tree_lookup(QTree *tree,
gconstpointer key)
{
return g_tree_lookup(tree, key);
}
static inline gboolean q_tree_lookup_extended(QTree *tree,
gconstpointer lookup_key,
gpointer *orig_key,
gpointer *value)
{
return g_tree_lookup_extended(tree, lookup_key, orig_key, value);
}
static inline void q_tree_foreach(QTree *tree,
GTraverseFunc func,
gpointer user_data)
{
return g_tree_foreach(tree, func, user_data);
}
static inline gpointer q_tree_search(QTree *tree,
GCompareFunc search_func,
gconstpointer user_data)
{
return g_tree_search(tree, search_func, user_data);
}
static inline gint q_tree_height(QTree *tree)
{
return g_tree_height(tree);
}
static inline gint q_tree_nnodes(QTree *tree)
{
return g_tree_nnodes(tree);
}
#endif /* HAVE_GLIB_WITH_SLICE_ALLOCATOR */
#endif /* QEMU_QTREE_H */

View File

@ -508,6 +508,10 @@ glib = declare_dependency(compile_args: config_host['GLIB_CFLAGS'].split(),
}) })
# override glib dep with the configure results (for subprojects) # override glib dep with the configure results (for subprojects)
meson.override_dependency('glib-2.0', glib) meson.override_dependency('glib-2.0', glib)
# pass down whether Glib has the slice allocator
if config_host.has_key('HAVE_GLIB_WITH_SLICE_ALLOCATOR')
config_host_data.set('HAVE_GLIB_WITH_SLICE_ALLOCATOR', true)
endif
gio = not_found gio = not_found
gdbus_codegen = not_found gdbus_codegen = not_found

View File

@ -9,6 +9,10 @@ xbzrle_bench = executable('xbzrle-bench',
dependencies: [qemuutil,migration]) dependencies: [qemuutil,migration])
endif endif
qtree_bench = executable('qtree-bench',
sources: 'qtree-bench.c',
dependencies: [qemuutil])
executable('atomic_add-bench', executable('atomic_add-bench',
sources: files('atomic_add-bench.c'), sources: files('atomic_add-bench.c'),
dependencies: [qemuutil], dependencies: [qemuutil],

286
tests/bench/qtree-bench.c Normal file
View File

@ -0,0 +1,286 @@
/* 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;
}

View File

@ -36,6 +36,7 @@ tests = {
'test-rcu-slist': [], 'test-rcu-slist': [],
'test-qdist': [], 'test-qdist': [],
'test-qht': [], 'test-qht': [],
'test-qtree': [],
'test-bitops': [], 'test-bitops': [],
'test-bitcnt': [], 'test-bitcnt': [],
'test-qgraph': ['../qtest/libqos/qgraph.c'], 'test-qgraph': ['../qtest/libqos/qgraph.c'],

333
tests/unit/test-qtree.c Normal file
View File

@ -0,0 +1,333 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* Tests for QTree.
* Original source: glib
* https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/tests/tree.c
* LGPL license.
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*/
#include "qemu/osdep.h"
#include "qemu/qtree.h"
static gint my_compare(gconstpointer a, gconstpointer b)
{
const char *cha = a;
const char *chb = b;
return *cha - *chb;
}
static gint my_compare_with_data(gconstpointer a,
gconstpointer b,
gpointer user_data)
{
const char *cha = a;
const char *chb = b;
/* just check that we got the right data */
g_assert(GPOINTER_TO_INT(user_data) == 123);
return *cha - *chb;
}
static gint my_search(gconstpointer a, gconstpointer b)
{
return my_compare(b, a);
}
static gpointer destroyed_key;
static gpointer destroyed_value;
static guint destroyed_key_count;
static guint destroyed_value_count;
static void my_key_destroy(gpointer key)
{
destroyed_key = key;
destroyed_key_count++;
}
static void my_value_destroy(gpointer value)
{
destroyed_value = value;
destroyed_value_count++;
}
static gint my_traverse(gpointer key, gpointer value, gpointer data)
{
char *ch = key;
g_assert((*ch) > 0);
if (*ch == 'd') {
return TRUE;
}
return FALSE;
}
char chars[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
char chars2[] =
"0123456789"
"abcdefghijklmnopqrstuvwxyz";
static gint check_order(gpointer key, gpointer value, gpointer data)
{
char **p = data;
char *ch = key;
g_assert(**p == *ch);
(*p)++;
return FALSE;
}
static void test_tree_search(void)
{
gint i;
QTree *tree;
gboolean removed;
gchar c;
gchar *p, *d;
tree = q_tree_new_with_data(my_compare_with_data, GINT_TO_POINTER(123));
for (i = 0; chars[i]; i++) {
q_tree_insert(tree, &chars[i], &chars[i]);
}
q_tree_foreach(tree, my_traverse, NULL);
g_assert(q_tree_nnodes(tree) == strlen(chars));
g_assert(q_tree_height(tree) == 6);
p = chars;
q_tree_foreach(tree, check_order, &p);
for (i = 0; i < 26; i++) {
removed = q_tree_remove(tree, &chars[i + 10]);
g_assert(removed);
}
c = '\0';
removed = q_tree_remove(tree, &c);
g_assert(!removed);
q_tree_foreach(tree, my_traverse, NULL);
g_assert(q_tree_nnodes(tree) == strlen(chars2));
g_assert(q_tree_height(tree) == 6);
p = chars2;
q_tree_foreach(tree, check_order, &p);
for (i = 25; i >= 0; i--) {
q_tree_insert(tree, &chars[i + 10], &chars[i + 10]);
}
p = chars;
q_tree_foreach(tree, check_order, &p);
c = '0';
p = q_tree_lookup(tree, &c);
g_assert(p && *p == c);
g_assert(q_tree_lookup_extended(tree, &c, (gpointer *)&d, (gpointer *)&p));
g_assert(c == *d && c == *p);
c = 'A';
p = q_tree_lookup(tree, &c);
g_assert(p && *p == c);
c = 'a';
p = q_tree_lookup(tree, &c);
g_assert(p && *p == c);
c = 'z';
p = q_tree_lookup(tree, &c);
g_assert(p && *p == c);
c = '!';
p = q_tree_lookup(tree, &c);
g_assert(p == NULL);
c = '=';
p = q_tree_lookup(tree, &c);
g_assert(p == NULL);
c = '|';
p = q_tree_lookup(tree, &c);
g_assert(p == NULL);
c = '0';
p = q_tree_search(tree, my_search, &c);
g_assert(p && *p == c);
c = 'A';
p = q_tree_search(tree, my_search, &c);
g_assert(p && *p == c);
c = 'a';
p = q_tree_search(tree, my_search, &c);
g_assert(p && *p == c);
c = 'z';
p = q_tree_search(tree, my_search, &c);
g_assert(p && *p == c);
c = '!';
p = q_tree_search(tree, my_search, &c);
g_assert(p == NULL);
c = '=';
p = q_tree_search(tree, my_search, &c);
g_assert(p == NULL);
c = '|';
p = q_tree_search(tree, my_search, &c);
g_assert(p == NULL);
q_tree_destroy(tree);
}
static void test_tree_remove(void)
{
QTree *tree;
char c, d;
gint i;
gboolean removed;
tree = q_tree_new_full((GCompareDataFunc)my_compare, NULL,
my_key_destroy,
my_value_destroy);
for (i = 0; chars[i]; i++) {
q_tree_insert(tree, &chars[i], &chars[i]);
}
c = '0';
q_tree_insert(tree, &c, &c);
g_assert(destroyed_key == &c);
g_assert(destroyed_value == &chars[0]);
destroyed_key = NULL;
destroyed_value = NULL;
d = '1';
q_tree_replace(tree, &d, &d);
g_assert(destroyed_key == &chars[1]);
g_assert(destroyed_value == &chars[1]);
destroyed_key = NULL;
destroyed_value = NULL;
c = '2';
removed = q_tree_remove(tree, &c);
g_assert(removed);
g_assert(destroyed_key == &chars[2]);
g_assert(destroyed_value == &chars[2]);
destroyed_key = NULL;
destroyed_value = NULL;
c = '3';
removed = q_tree_steal(tree, &c);
g_assert(removed);
g_assert(destroyed_key == NULL);
g_assert(destroyed_value == NULL);
const gchar *remove = "omkjigfedba";
for (i = 0; remove[i]; i++) {
removed = q_tree_remove(tree, &remove[i]);
g_assert(removed);
}
q_tree_destroy(tree);
}
static void test_tree_destroy(void)
{
QTree *tree;
gint i;
tree = q_tree_new(my_compare);
for (i = 0; chars[i]; i++) {
q_tree_insert(tree, &chars[i], &chars[i]);
}
g_assert(q_tree_nnodes(tree) == strlen(chars));
g_test_message("nnodes: %d", q_tree_nnodes(tree));
q_tree_ref(tree);
q_tree_destroy(tree);
g_test_message("nnodes: %d", q_tree_nnodes(tree));
g_assert(q_tree_nnodes(tree) == 0);
q_tree_unref(tree);
}
static void test_tree_insert(void)
{
QTree *tree;
gchar *p;
gint i;
gchar *scrambled;
tree = q_tree_new(my_compare);
for (i = 0; chars[i]; i++) {
q_tree_insert(tree, &chars[i], &chars[i]);
}
p = chars;
q_tree_foreach(tree, check_order, &p);
q_tree_unref(tree);
tree = q_tree_new(my_compare);
for (i = strlen(chars) - 1; i >= 0; i--) {
q_tree_insert(tree, &chars[i], &chars[i]);
}
p = chars;
q_tree_foreach(tree, check_order, &p);
q_tree_unref(tree);
tree = q_tree_new(my_compare);
scrambled = g_strdup(chars);
for (i = 0; i < 30; i++) {
gchar tmp;
gint a, b;
a = g_random_int_range(0, strlen(scrambled));
b = g_random_int_range(0, strlen(scrambled));
tmp = scrambled[a];
scrambled[a] = scrambled[b];
scrambled[b] = tmp;
}
for (i = 0; scrambled[i]; i++) {
q_tree_insert(tree, &scrambled[i], &scrambled[i]);
}
p = chars;
q_tree_foreach(tree, check_order, &p);
g_free(scrambled);
q_tree_unref(tree);
}
int main(int argc, char *argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qtree/search", test_tree_search);
g_test_add_func("/qtree/remove", test_tree_remove);
g_test_add_func("/qtree/destroy", test_tree_destroy);
g_test_add_func("/qtree/insert", test_tree_insert);
return g_test_run();
}

View File

@ -26,6 +26,7 @@ util_ss.add(when: 'CONFIG_WIN32', if_true: files('oslib-win32.c'))
util_ss.add(when: 'CONFIG_WIN32', if_true: files('qemu-thread-win32.c')) util_ss.add(when: 'CONFIG_WIN32', if_true: files('qemu-thread-win32.c'))
util_ss.add(when: 'CONFIG_WIN32', if_true: winmm) util_ss.add(when: 'CONFIG_WIN32', if_true: winmm)
util_ss.add(when: 'CONFIG_WIN32', if_true: pathcch) util_ss.add(when: 'CONFIG_WIN32', if_true: pathcch)
util_ss.add(when: 'HAVE_GLIB_WITH_SLICE_ALLOCATOR', if_true: files('qtree.c'))
util_ss.add(files('envlist.c', 'path.c', 'module.c')) util_ss.add(files('envlist.c', 'path.c', 'module.c'))
util_ss.add(files('host-utils.c')) util_ss.add(files('host-utils.c'))
util_ss.add(files('bitmap.c', 'bitops.c')) util_ss.add(files('bitmap.c', 'bitops.c'))

1390
util/qtree.c Normal file

File diff suppressed because it is too large Load Diff