qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: "Alex Bennée" <alex.bennee@linaro.org>
To: Sergey Fedorov <serge.fdrv@gmail.com>
Cc: "Emilio G. Cota" <cota@braap.org>,
	QEMU Developers <qemu-devel@nongnu.org>,
	MTTCG Devel <mttcg@listserver.greensocs.com>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Richard Henderson <rth@twiddle.net>
Subject: Re: [Qemu-devel] [PATCH v6 10/15] qht: QEMU's fast, resizable and scalable Hash Table
Date: Tue, 31 May 2016 08:46:51 +0100	[thread overview]
Message-ID: <877fea8yac.fsf@linaro.org> (raw)
In-Reply-To: <574B487B.5080200@gmail.com>


Sergey Fedorov <serge.fdrv@gmail.com> writes:

> On 25/05/16 04:13, Emilio G. Cota wrote:
>> diff --git a/include/qemu/qht.h b/include/qemu/qht.h
>> new file mode 100644
>> index 0000000..aec60aa
>> --- /dev/null
>> +++ b/include/qemu/qht.h
>> @@ -0,0 +1,183 @@
> (snip)
>> +/**
>> + * qht_init - Initialize a QHT
>> + * @ht: QHT to be initialized
>> + * @n_elems: number of entries the hash table should be optimized for.
>> + * @mode: bitmask with OR'ed QHT_MODE_*
>> + */
>> +void qht_init(struct qht *ht, size_t n_elems, unsigned int mode);
>
> First of all, thank you for spending your time on the documentation of
> the API!
>
> I was just wondering if it could be worthwhile to pass a hash function
> when initializing a QHT. Then we could have variants of qht_insert(),
> qht_remove() and qht_lookup() which does not require a computed hash
> value but call the function by themselves. This could make sense since a
> hash value passed the the functions should always be exactly the same
> for the same object.

Wouldn't this be for an expansion of the API when we actually have
something that would use it that way?

>
> (snip)
>> +/**
>> + * qht_remove - remove a pointer from the hash table
>> + * @ht: QHT to remove from
>> + * @p: pointer to be removed
>> + * @hash: hash corresponding to @p
>> + *
>> + * Attempting to remove a NULL @p is a bug.
>> + *
>> + * Just-removed @p pointers cannot be immediately freed; they need to remain
>> + * valid until the end of the RCU grace period in which qht_remove() is called.
>> + * This guarantees that concurrent lookups will always compare against valid
>> + * data.
>
> Mention rcu_call1()/call_rcu()/g_free_rcu()?
>
>> + *
>> + * Returns true on success.
>> + * Returns false if the @p-@hash pair was not found.
>> + */
>> +bool qht_remove(struct qht *ht, const void *p, uint32_t hash);
>> +
> (snip)
>> diff --git a/util/qht.c b/util/qht.c
>> new file mode 100644
>> index 0000000..ca5a620
>> --- /dev/null
>> +++ b/util/qht.c
>> @@ -0,0 +1,837 @@
> (snip)
>> +/* trigger a resize when n_added_buckets > n_buckets / div */
>> +#define QHT_NR_ADDED_BUCKETS_THRESHOLD_DIV 8
>
> Just out of curiosity, how did you get this number?
>
>> +
>> +static void qht_do_resize(struct qht *ht, struct qht_map *new);
>> +static void qht_grow_maybe(struct qht *ht);
>
> qht_grow_maybe() is used just once. Please consider reordering of
> definitions and removing this forward declaration.
>
> (snip)
>> +
>> +/* call with head->lock held */
>> +static bool qht_insert__locked(struct qht *ht, struct qht_map *map,
>> +                               struct qht_bucket *head, void *p, uint32_t hash,
>> +                               bool *needs_resize)
>> +{
>> +    struct qht_bucket *b = head;
>> +    struct qht_bucket *prev = NULL;
>> +    struct qht_bucket *new = NULL;
>> +    int i;
>> +
>> +    for (;;) {
>> +        if (b == NULL) {
>> +            b = qemu_memalign(QHT_BUCKET_ALIGN, sizeof(*b));
>> +            memset(b, 0, sizeof(*b));
>> +            new = b;
>> +            atomic_inc(&map->n_added_buckets);
>> +            if (unlikely(qht_map_needs_resize(map)) && needs_resize) {
>> +                *needs_resize = true;
>> +            }
>> +        }
>> +        for (i = 0; i < QHT_BUCKET_ENTRIES; i++) {
>> +            if (b->pointers[i]) {
>> +                if (unlikely(b->pointers[i] == p)) {
>> +                    return false;
>> +                }
>> +                continue;
>> +            }
>> +            /* found an empty key: acquire the seqlock and write */
>> +            seqlock_write_begin(&head->sequence);
>> +            if (new) {
>> +                atomic_rcu_set(&prev->next, b);
>> +            }
>> +            b->hashes[i] = hash;
>> +            atomic_set(&b->pointers[i], p);
>> +            seqlock_write_end(&head->sequence);
>> +            return true;
>> +        }
>> +        prev = b;
>> +        b = b->next;
>> +    }
>> +}
>
> Here is my attempt:
>
> static bool qht_insert__locked(struct qht *ht, struct qht_map
> *map,
>                                struct qht_bucket *head, void *p,
> uint32_t hash,
>                                bool
> *needs_resize)
> {
>
>     struct qht_bucket **bpp = &head,
> *new;
>     int i =
> 0;
>
>
>     do
> {
>         while (i < QHT_BUCKET_ENTRIES)
> {
>             if ((*bpp)->pointers[i])
> {
>                 if (unlikely((*bpp)->pointers[i] == p))
> {
>                     return
> false;
>
> }
>
> i++;
>
> continue;
>
> }
>             goto
> found;
>
> }
>         bpp =
> &(*bpp)->next;
>         i =
> 0;
>     } while
> (*bpp);
>
>
>     new = qemu_memalign(QHT_BUCKET_ALIGN,
> sizeof(*new));
>     memset(new, 0,
> sizeof(*new));
>     atomic_rcu_set(bpp,
> new);
>
> atomic_inc(&map->n_added_buckets);
>     if (unlikely(qht_map_needs_resize(map)) && needs_resize)
> {
>         *needs_resize =
> true;
>
> }
> found:
>
>     /* found an empty key: acquire the seqlock and write
> */
>
> seqlock_write_begin(&head->sequence);
>     (*bpp)->hashes[i] =
> hash;
>     atomic_set(&(*bpp)->pointers[i],
> p);
>
> seqlock_write_end(&head->sequence);
>     return
> true;
> }
>
>
> Feel free to use it as you wish.
>
>>
> (snip)
>> +/*
>> + * Find the last valid entry in @head, and swap it with @orig[pos], which has
>> + * just been invalidated.
>> + */
>> +static inline void qht_bucket_fill_hole(struct qht_bucket *orig, int pos)
>> +{
>> +    struct qht_bucket *b = orig;
>> +    struct qht_bucket *prev = NULL;
>> +    int i;
>> +
>> +    if (qht_entry_is_last(orig, pos)) {
>> +        orig->hashes[pos] = 0;
>> +        atomic_set(&orig->pointers[pos], NULL);
>> +        return;
>> +    }
>> +    do {
>> +        for (i = 0; i < QHT_BUCKET_ENTRIES; i++) {
>> +            if (b->pointers[i]) {
>> +                continue;
>> +            }
>> +            if (i > 0) {
>> +                return qht_entry_move(orig, pos, b, i - 1);
>> +            }
>> +            qht_debug_assert(prev);
>
> 'prev' can be NULL if this is the first iteration.
>
>> +            return qht_entry_move(orig, pos, prev, QHT_BUCKET_ENTRIES - 1);
>> +        }
>> +        prev = b;
>> +        b = b->next;
>> +    } while (b);
>> +    /* no free entries other than orig[pos], so swap it with the last one */
>> +    qht_entry_move(orig, pos, prev, QHT_BUCKET_ENTRIES - 1);
>> +}
>> +
>> +/* call with b->lock held */
>> +static inline
>> +bool qht_remove__locked(struct qht_map *map, struct qht_bucket *head,
>> +                        const void *p, uint32_t hash)
>> +{
>> +    struct qht_bucket *b = head;
>> +    int i;
>> +
>> +    do {
>> +        for (i = 0; i < QHT_BUCKET_ENTRIES; i++) {
>> +            void *q = b->pointers[i];
>> +
>> +            if (unlikely(q == NULL)) {
>> +                return false;
>> +            }
>> +            if (q == p) {
>> +                qht_debug_assert(b->hashes[i] == hash);
>> +                seqlock_write_begin(&head->sequence);
>> +                qht_bucket_fill_hole(b, i);
>
> "Fill hole" doesn't correspond to the function's new job since there's
> no hole. "Remove entry" would make more sense, I think.
>
>> +                seqlock_write_end(&head->sequence);
>> +                return true;
>> +            }
>> +        }
>> +        b = b->next;
>> +    } while (b);
>> +    return false;
>> +}
> (snip)
>> +/*
>> + * Call with ht->lock and all bucket locks held.
>> + *
>> + * Creating the @new map here would add unnecessary delay while all the locks
>> + * are held--holding up the bucket locks is particularly bad, since no writes
>> + * can occur while these are held. Thus, we let callers create the new map,
>> + * hopefully without the bucket locks held.
>> + */
>> +static void qht_do_resize(struct qht *ht, struct qht_map *new)
>> +{
>> +    struct qht_map *old;
>> +
>> +    old = ht->map;
>> +    g_assert_cmpuint(new->n_buckets, !=, old->n_buckets);
>> +
>> +    qht_map_iter__all_locked(ht, old, qht_map_copy, new);
>> +    qht_map_debug__all_locked(new);
>> +
>> +    atomic_rcu_set(&ht->map, new);
>> +    call_rcu1(&old->rcu, qht_map_reclaim);
>
> call_rcu() macro is a more convenient way to do this and you wouldn't
> need qht_map_reclaim().
>
>> +}
>
> Kind regards,
> Sergey


--
Alex Bennée

  parent reply	other threads:[~2016-05-31  7:46 UTC|newest]

Thread overview: 63+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-05-25  1:13 [Qemu-devel] [PATCH v6 00/15] tb hash improvements Emilio G. Cota
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 01/15] compiler.h: add QEMU_ALIGNED() to enforce struct alignment Emilio G. Cota
2016-05-27 19:54   ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 02/15] seqlock: remove optional mutex Emilio G. Cota
2016-05-27 19:55   ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 03/15] seqlock: rename write_lock/unlock to write_begin/end Emilio G. Cota
2016-05-27 19:59   ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 04/15] include/processor.h: define cpu_relax() Emilio G. Cota
2016-05-27 20:53   ` Sergey Fedorov
2016-05-27 21:10     ` Emilio G. Cota
2016-05-28 12:35       ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 05/15] qemu-thread: add simple test-and-set spinlock Emilio G. Cota
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 06/15] exec: add tb_hash_func5, derived from xxhash Emilio G. Cota
2016-05-28 12:36   ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 07/15] tb hash: hash phys_pc, pc, and flags with xxhash Emilio G. Cota
2016-05-28 12:39   ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 08/15] qdist: add module to represent frequency distributions of data Emilio G. Cota
2016-05-28 18:15   ` Sergey Fedorov
2016-06-03 17:22     ` Emilio G. Cota
2016-06-03 17:29       ` Sergey Fedorov
2016-06-03 17:46         ` Sergey Fedorov
2016-06-06 23:40           ` Emilio G. Cota
2016-06-07 14:06             ` Sergey Fedorov
2016-06-07 22:53               ` Emilio G. Cota
2016-06-08 13:09                 ` Sergey Fedorov
2016-06-07  1:05     ` Emilio G. Cota
2016-06-07 15:56       ` Sergey Fedorov
2016-06-08  0:02         ` Emilio G. Cota
2016-06-08 14:10           ` Sergey Fedorov
2016-06-08 18:06             ` Emilio G. Cota
2016-06-08 18:18               ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 09/15] qdist: add test program Emilio G. Cota
2016-05-28 18:56   ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 10/15] qht: QEMU's fast, resizable and scalable Hash Table Emilio G. Cota
2016-05-29 19:52   ` Sergey Fedorov
2016-05-29 19:55     ` Sergey Fedorov
2016-05-31  7:46     ` Alex Bennée [this message]
2016-06-01 20:53       ` Sergey Fedorov
2016-06-03  9:18     ` Emilio G. Cota
2016-06-03 15:19       ` Sergey Fedorov
2016-06-03 11:01     ` Emilio G. Cota
2016-06-03 15:34       ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 11/15] qht: add test program Emilio G. Cota
2016-05-29 20:15   ` Sergey Fedorov
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 12/15] qht: add qht-bench, a performance benchmark Emilio G. Cota
2016-05-29 20:45   ` Sergey Fedorov
2016-06-03 11:41     ` Emilio G. Cota
2016-06-03 15:41       ` Sergey Fedorov
2016-05-31 15:12   ` Alex Bennée
2016-05-31 16:44     ` Emilio G. Cota
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 13/15] qht: add test-qht-par to invoke qht-bench from 'check' target Emilio G. Cota
2016-05-29 20:53   ` Sergey Fedorov
2016-06-03 11:07     ` Emilio G. Cota
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 14/15] tb hash: track translated blocks with qht Emilio G. Cota
2016-05-29 21:09   ` Sergey Fedorov
2016-05-31  8:39   ` Alex Bennée
2016-05-25  1:13 ` [Qemu-devel] [PATCH v6 15/15] translate-all: add tb hash bucket info to 'info jit' dump Emilio G. Cota
2016-05-29 21:14   ` Sergey Fedorov
2016-06-08  6:25 ` [Qemu-devel] [PATCH v6 00/15] tb hash improvements Alex Bennée
2016-06-08 15:16   ` Emilio G. Cota
2016-06-08 15:35   ` Richard Henderson
2016-06-08 15:37     ` Sergey Fedorov
2016-06-08 16:45       ` Alex Bennée

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=877fea8yac.fsf@linaro.org \
    --to=alex.bennee@linaro.org \
    --cc=cota@braap.org \
    --cc=mttcg@listserver.greensocs.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=rth@twiddle.net \
    --cc=serge.fdrv@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).