Skip to content

Commit

Permalink
Performance optimization of AVL tree comparator functions
Browse files Browse the repository at this point in the history
perf: 2.75x faster ddt_entry_compare()
    First 256bits of ddt_key_t is a block checksum, which are expected
to be close to random data. Hence, on average, comparison only needs to
look at first few bytes of the keys. To reduce number of conditional
jump instructions, the result is computed as: sign(memcmp(k1, k2)).

Sign of an integer 'a' can be obtained as: `(0 < a) - (a < 0)` := {-1, 0, 1} ,
which is computed efficiently.  Synthetic performance evaluation of
original and new algorithm over 1G random keys on 2.6GHz Intel(R) Xeon(R)
CPU E5-2660 v3:

old	6.85789 s
new	2.49089 s

perf: 2.8x faster vdev_queue_offset_compare() and vdev_queue_timestamp_compare()
    Compute the result directly instead of using conditionals

perf: zfs_range_compare()
    Speedup between 1.1x - 2.5x, depending on compiler version and
optimization level.

perf: spa_error_entry_compare()
    `bcmp()` is not suitable for comparator use. Use `memcmp()` instead.

perf: 2.8x faster metaslab_compare() and metaslab_rangesize_compare()
perf: 2.8x faster zil_bp_compare()
perf: 2.8x faster mze_compare()
perf: faster dbuf_compare()
perf: faster compares in spa_misc
perf: 2.8x faster layout_hash_compare()
perf: 2.8x faster space_reftree_compare()
perf: libzfs: faster avl tree comparators
perf: guid_compare()
perf: dsl_deadlist_compare()
perf: perm_set_compare()
perf: 2x faster range_tree_seg_compare()
perf: faster unique_compare()
perf: faster vdev_cache _compare()
perf: faster vdev_uberblock_compare()
perf: faster fuid _compare()
perf: faster zfs_znode_hold_compare()

Signed-off-by: Gvozden Neskovic <[email protected]>
Signed-off-by: Richard Elling <[email protected]>
Signed-off-by: Brian Behlendorf <[email protected]>
Closes #5033
  • Loading branch information
ironMann authored and behlendorf committed Aug 31, 2016
1 parent 9d69e9b commit ee36c70
Show file tree
Hide file tree
Showing 25 changed files with 148 additions and 274 deletions.
7 changes: 7 additions & 0 deletions include/sys/avl.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,13 @@ extern "C" {
* as is needed for any linked list implementation.
*/

/*
* AVL comparator helpers
*/
#define AVL_ISIGN(a) (((a) > 0) - ((a) < 0))
#define AVL_CMP(a, b) (((a) > (b)) - ((a) < (b)))
#define AVL_PCMP(a, b) \
(((uintptr_t)(a) > (uintptr_t)(b)) - ((uintptr_t)(a) < (uintptr_t)(b)))

/*
* Type used for the root of the AVL tree.
Expand Down
1 change: 1 addition & 0 deletions include/sys/zfs_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
*/

#define noinline __attribute__((noinline))
#define likely(x) __builtin_expect((x), 1)

/*
* Debugging
Expand Down
8 changes: 3 additions & 5 deletions lib/libzfs/libzfs_dataset.c
Original file line number Diff line number Diff line change
Expand Up @@ -674,15 +674,13 @@ typedef struct mnttab_node {
static int
libzfs_mnttab_cache_compare(const void *arg1, const void *arg2)
{
const mnttab_node_t *mtn1 = arg1;
const mnttab_node_t *mtn2 = arg2;
const mnttab_node_t *mtn1 = (const mnttab_node_t *)arg1;
const mnttab_node_t *mtn2 = (const mnttab_node_t *)arg2;
int rv;

rv = strcmp(mtn1->mtn_mt.mnt_special, mtn2->mtn_mt.mnt_special);

if (rv == 0)
return (0);
return (rv > 0 ? 1 : -1);
return (AVL_ISIGN(rv));
}

void
Expand Down
7 changes: 1 addition & 6 deletions lib/libzfs/libzfs_iter.c
Original file line number Diff line number Diff line change
Expand Up @@ -272,12 +272,7 @@ zfs_snapshot_compare(const void *larg, const void *rarg)
lcreate = zfs_prop_get_int(l, ZFS_PROP_CREATETXG);
rcreate = zfs_prop_get_int(r, ZFS_PROP_CREATETXG);

if (lcreate < rcreate)
return (-1);
else if (lcreate > rcreate)
return (+1);
else
return (0);
return (AVL_CMP(lcreate, rcreate));
}

int
Expand Down
11 changes: 3 additions & 8 deletions lib/libzfs/libzfs_sendrecv.c
Original file line number Diff line number Diff line change
Expand Up @@ -475,15 +475,10 @@ typedef struct fsavl_node {
static int
fsavl_compare(const void *arg1, const void *arg2)
{
const fsavl_node_t *fn1 = arg1;
const fsavl_node_t *fn2 = arg2;
const fsavl_node_t *fn1 = (const fsavl_node_t *)arg1;
const fsavl_node_t *fn2 = (const fsavl_node_t *)arg2;

if (fn1->fn_guid > fn2->fn_guid)
return (+1);
else if (fn1->fn_guid < fn2->fn_guid)
return (-1);
else
return (0);
return (AVL_CMP(fn1->fn_guid, fn2->fn_guid));
}

/*
Expand Down
25 changes: 17 additions & 8 deletions module/zfs/ddt.c
Original file line number Diff line number Diff line change
Expand Up @@ -811,23 +811,32 @@ ddt_prefetch(spa_t *spa, const blkptr_t *bp)
}
}

/*
* Opaque struct used for ddt_key comparison
*/
#define DDT_KEY_CMP_LEN (sizeof (ddt_key_t) / sizeof (uint16_t))

typedef struct ddt_key_cmp {
uint16_t u16[DDT_KEY_CMP_LEN];
} ddt_key_cmp_t;

int
ddt_entry_compare(const void *x1, const void *x2)
{
const ddt_entry_t *dde1 = x1;
const ddt_entry_t *dde2 = x2;
const uint64_t *u1 = (const uint64_t *)&dde1->dde_key;
const uint64_t *u2 = (const uint64_t *)&dde2->dde_key;
const ddt_key_cmp_t *k1 = (const ddt_key_cmp_t *)&dde1->dde_key;
const ddt_key_cmp_t *k2 = (const ddt_key_cmp_t *)&dde2->dde_key;
int32_t cmp = 0;
int i;

for (i = 0; i < DDT_KEY_WORDS; i++) {
if (u1[i] < u2[i])
return (-1);
if (u1[i] > u2[i])
return (1);
for (i = 0; i < DDT_KEY_CMP_LEN; i++) {
cmp = (int32_t)k1->u16[i] - (int32_t)k2->u16[i];
if (likely(cmp))
break;
}

return (0);
return (AVL_ISIGN(cmp));
}

static ddt_t *
Expand Down
10 changes: 3 additions & 7 deletions module/zfs/dmu_send.c
Original file line number Diff line number Diff line change
Expand Up @@ -1793,14 +1793,10 @@ typedef struct guid_map_entry {
static int
guid_compare(const void *arg1, const void *arg2)
{
const guid_map_entry_t *gmep1 = arg1;
const guid_map_entry_t *gmep2 = arg2;
const guid_map_entry_t *gmep1 = (const guid_map_entry_t *)arg1;
const guid_map_entry_t *gmep2 = (const guid_map_entry_t *)arg2;

if (gmep1->guid < gmep2->guid)
return (-1);
else if (gmep1->guid > gmep2->guid)
return (1);
return (0);
return (AVL_CMP(gmep1->guid, gmep2->guid));
}

static void
Expand Down
26 changes: 7 additions & 19 deletions module/zfs/dnode.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,13 @@ dbuf_compare(const void *x1, const void *x2)
const dmu_buf_impl_t *d1 = x1;
const dmu_buf_impl_t *d2 = x2;

if (d1->db_level < d2->db_level) {
return (-1);
}
if (d1->db_level > d2->db_level) {
return (1);
}
int cmp = AVL_CMP(d1->db_level, d2->db_level);
if (likely(cmp))
return (cmp);

if (d1->db_blkid < d2->db_blkid) {
return (-1);
}
if (d1->db_blkid > d2->db_blkid) {
return (1);
}
cmp = AVL_CMP(d1->db_blkid, d2->db_blkid);
if (likely(cmp))
return (cmp);

if (d1->db_state == DB_SEARCH) {
ASSERT3S(d2->db_state, !=, DB_SEARCH);
Expand All @@ -91,13 +85,7 @@ dbuf_compare(const void *x1, const void *x2)
return (1);
}

if ((uintptr_t)d1 < (uintptr_t)d2) {
return (-1);
}
if ((uintptr_t)d1 > (uintptr_t)d2) {
return (1);
}
return (0);
return (AVL_PCMP(d1, d2));
}

/* ARGSUSED */
Expand Down
11 changes: 3 additions & 8 deletions module/zfs/dsl_deadlist.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,10 @@
static int
dsl_deadlist_compare(const void *arg1, const void *arg2)
{
const dsl_deadlist_entry_t *dle1 = arg1;
const dsl_deadlist_entry_t *dle2 = arg2;
const dsl_deadlist_entry_t *dle1 = (const dsl_deadlist_entry_t *)arg1;
const dsl_deadlist_entry_t *dle2 = (const dsl_deadlist_entry_t *)arg2;

if (dle1->dle_mintxg < dle2->dle_mintxg)
return (-1);
else if (dle1->dle_mintxg > dle2->dle_mintxg)
return (+1);
else
return (0);
return (AVL_CMP(dle1->dle_mintxg, dle2->dle_mintxg));
}

static void
Expand Down
9 changes: 4 additions & 5 deletions module/zfs/dsl_deleg.c
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,13 @@ typedef struct perm_set {
static int
perm_set_compare(const void *arg1, const void *arg2)
{
const perm_set_t *node1 = arg1;
const perm_set_t *node2 = arg2;
const perm_set_t *node1 = (const perm_set_t *)arg1;
const perm_set_t *node2 = (const perm_set_t *)arg2;
int val;

val = strcmp(node1->p_setname, node2->p_setname);
if (val == 0)
return (0);
return (val > 0 ? 1 : -1);

return (AVL_ISIGN(val));
}

/*
Expand Down
38 changes: 11 additions & 27 deletions module/zfs/metaslab.c
Original file line number Diff line number Diff line change
Expand Up @@ -399,25 +399,16 @@ metaslab_class_expandable_space(metaslab_class_t *mc)
static int
metaslab_compare(const void *x1, const void *x2)
{
const metaslab_t *m1 = x1;
const metaslab_t *m2 = x2;
const metaslab_t *m1 = (const metaslab_t *)x1;
const metaslab_t *m2 = (const metaslab_t *)x2;

if (m1->ms_weight < m2->ms_weight)
return (1);
if (m1->ms_weight > m2->ms_weight)
return (-1);
int cmp = AVL_CMP(m2->ms_weight, m1->ms_weight);
if (likely(cmp))
return (cmp);

/*
* If the weights are identical, use the offset to force uniqueness.
*/
if (m1->ms_start < m2->ms_start)
return (-1);
if (m1->ms_start > m2->ms_start)
return (1);
IMPLY(AVL_CMP(m1->ms_start, m2->ms_start) == 0, m1 == m2);

ASSERT3P(m1, ==, m2);

return (0);
return (AVL_CMP(m1->ms_start, m2->ms_start));
}

/*
Expand Down Expand Up @@ -795,18 +786,11 @@ metaslab_rangesize_compare(const void *x1, const void *x2)
uint64_t rs_size1 = r1->rs_end - r1->rs_start;
uint64_t rs_size2 = r2->rs_end - r2->rs_start;

if (rs_size1 < rs_size2)
return (-1);
if (rs_size1 > rs_size2)
return (1);
int cmp = AVL_CMP(rs_size1, rs_size2);
if (likely(cmp))
return (cmp);

if (r1->rs_start < r2->rs_start)
return (-1);

if (r1->rs_start > r2->rs_start)
return (1);

return (0);
return (AVL_CMP(r1->rs_start, r2->rs_start));
}

/*
Expand Down
19 changes: 6 additions & 13 deletions module/zfs/range_tree.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,20 +111,13 @@ range_tree_stat_decr(range_tree_t *rt, range_seg_t *rs)
static int
range_tree_seg_compare(const void *x1, const void *x2)
{
const range_seg_t *r1 = x1;
const range_seg_t *r2 = x2;
const range_seg_t *r1 = (const range_seg_t *)x1;
const range_seg_t *r2 = (const range_seg_t *)x2;

if (r1->rs_start < r2->rs_start) {
if (r1->rs_end > r2->rs_start)
return (0);
return (-1);
}
if (r1->rs_start > r2->rs_start) {
if (r1->rs_start < r2->rs_end)
return (0);
return (1);
}
return (0);
ASSERT3U(r1->rs_start, <=, r1->rs_end);
ASSERT3U(r2->rs_start, <=, r2->rs_end);

return ((r1->rs_start >= r2->rs_end) - (r1->rs_end <= r2->rs_start));
}

range_tree_t *
Expand Down
28 changes: 10 additions & 18 deletions module/zfs/sa.c
Original file line number Diff line number Diff line change
Expand Up @@ -241,31 +241,23 @@ sa_cache_fini(void)
static int
layout_num_compare(const void *arg1, const void *arg2)
{
const sa_lot_t *node1 = arg1;
const sa_lot_t *node2 = arg2;
const sa_lot_t *node1 = (const sa_lot_t *)arg1;
const sa_lot_t *node2 = (const sa_lot_t *)arg2;

if (node1->lot_num > node2->lot_num)
return (1);
else if (node1->lot_num < node2->lot_num)
return (-1);
return (0);
return (AVL_CMP(node1->lot_num, node2->lot_num));
}

static int
layout_hash_compare(const void *arg1, const void *arg2)
{
const sa_lot_t *node1 = arg1;
const sa_lot_t *node2 = arg2;
const sa_lot_t *node1 = (const sa_lot_t *)arg1;
const sa_lot_t *node2 = (const sa_lot_t *)arg2;

if (node1->lot_hash > node2->lot_hash)
return (1);
if (node1->lot_hash < node2->lot_hash)
return (-1);
if (node1->lot_instance > node2->lot_instance)
return (1);
if (node1->lot_instance < node2->lot_instance)
return (-1);
return (0);
int cmp = AVL_CMP(node1->lot_hash, node2->lot_hash);
if (likely(cmp))
return (cmp);

return (AVL_CMP(node1->lot_instance, node2->lot_instance));
}

boolean_t
Expand Down
13 changes: 4 additions & 9 deletions module/zfs/spa.c
Original file line number Diff line number Diff line change
Expand Up @@ -822,19 +822,14 @@ spa_change_guid(spa_t *spa)
static int
spa_error_entry_compare(const void *a, const void *b)
{
spa_error_entry_t *sa = (spa_error_entry_t *)a;
spa_error_entry_t *sb = (spa_error_entry_t *)b;
const spa_error_entry_t *sa = (const spa_error_entry_t *)a;
const spa_error_entry_t *sb = (const spa_error_entry_t *)b;
int ret;

ret = bcmp(&sa->se_bookmark, &sb->se_bookmark,
ret = memcmp(&sa->se_bookmark, &sb->se_bookmark,
sizeof (zbookmark_phys_t));

if (ret < 0)
return (-1);
else if (ret > 0)
return (1);
else
return (0);
return (AVL_ISIGN(ret));
}

/*
Expand Down
20 changes: 6 additions & 14 deletions module/zfs/spa_misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -797,18 +797,13 @@ typedef struct spa_aux {
int aux_count;
} spa_aux_t;

static int
static inline int
spa_aux_compare(const void *a, const void *b)
{
const spa_aux_t *sa = a;
const spa_aux_t *sb = b;
const spa_aux_t *sa = (const spa_aux_t *)a;
const spa_aux_t *sb = (const spa_aux_t *)b;

if (sa->aux_guid < sb->aux_guid)
return (-1);
else if (sa->aux_guid > sb->aux_guid)
return (1);
else
return (0);
return (AVL_CMP(sa->aux_guid, sb->aux_guid));
}

void
Expand Down Expand Up @@ -1774,11 +1769,8 @@ spa_name_compare(const void *a1, const void *a2)
int s;

s = strcmp(s1->spa_name, s2->spa_name);
if (s > 0)
return (1);
if (s < 0)
return (-1);
return (0);

return (AVL_ISIGN(s));
}

void
Expand Down
Loading

0 comments on commit ee36c70

Please sign in to comment.