On Sat, Oct 10, 2026 at 4:03 PM Joel Fernandes <[email protected]> wrote:
>
>
>
> > On Oct 9, 2026, at 11:07 PM, KunWu Chan <[email protected]> wrote:
> >
> > On Tue, Oct 6, 2026 at 1:51 PM Mathieu Desnoyers
> > <[email protected]> wrote:
> >>
> >>> On 2026-10-05 13:15, Kunwu Chan wrote:
> >>> Replace the per-waiter exact-match scan with a shared Bloom filter
> >>> built during the second drain pass: each observed non-wildcard
> >>> address sets three bits, so a waiter whose address hashes to an
> >>> unset bit can be completed without another slot walk.
> >>>
> >>> A Bloom filter has no false negatives, so an address absent from
> >>> the final drain scan cannot be held by an observed slot; false
> >>> positives only delay completion to a later scan cycle.
> >>>
> >>> The filter uses a one-page bitmap with three multiply-shift hash
> >>> functions.  It lives in the hazptr_scan_state and is only touched
> >>> by the scan kthread, so waiter state remains on the caller's stack
> >>> and no dynamic allocation is needed.
> >>>
> >>> Suggested-by: Boqun Feng <[email protected]>
> >>
> >> I actually suggested that to Boqun in the first place. Feel
> >> free to add my suggested-by tag as well. :)
> >>
> >> Did you run your Bloom filter in a test program on the side to
> >> see how it behaves with pointers, and confirm that the false
> >> positive rate is not too high due to hashing algorithm/constants
> >> concerns ?
> >>
> >
> > Hi Mathieu,
> >
> > I ran a standalone test using the hash functions and
> > constants from
> > the patch, with random,
> > page-aligned, cacheline-aligned, sequential, and two synthetic
> > kernel-like address patterns.
>
> kernel-like? Linux or CUDA? I have to say I find AI generated messages hard 
> to read.
>
> >
> > With 768 inserted addresses, the measured false-positive rates were
> > 0.025%–0.034% across
> > these patterns, compared with an ideal Bloom-filter estimate of about
> > 0.031%. I also tested larger
> > insertion counts as stress cases. The results were consistent across
> > three seeds,
> > with only minor variation for the random distribution.
>
> Why is the distribution random?
>
> >
> > These are synthetic address distributions rather than measurements
> > from actual kernel waiter
> > addresses, but they provide an initial check of the hash functions
> > against several structured
> > address patterns.
>
> Why not just provide the test code? Oh, but your AI  crutches did not ask you 
> to do that.

Hi Joel,

Tested on x86-64 Ubuntu.
 $./hazptr-bloom-test 1
hazptr Bloom filter FP-rate sanity test
bitmap 32768 bits, k=3, 1000000 queries/point, seed 0x1

n                  100      256      512      768     1024     2000
 5000    10000
theory        0.000001 0.000012 0.000096 0.000313 0.000717 0.004684
0.049553 0.215670
random        0.000001 0.000009 0.000092 0.000291 0.000702 0.004786
0.049593 0.214689
page-aligned  0.000000 0.000004 0.000071 0.000251 0.000664 0.004892
0.060966 0.307875
cacheline     0.000001 0.000012 0.000096 0.000343 0.000781 0.004930
0.048607 0.228268
sequential    0.000002 0.000017 0.000104 0.000317 0.000709 0.004717
0.060357 0.235745
hi-half1      0.000002 0.000015 0.000083 0.000298 0.000690 0.005251
0.060659 0.285610
hi-half2      0.000001 0.000017 0.000106 0.000330 0.000722 0.004991
0.059801 0.309890

Here's the standalone test code:
// SPDX-License-Identifier: GPL-2.0
/*
 * Standalone hazptr Bloom-filter false-positive test.
 * Requires 64-bit LP64 userspace and 4 KiB pages.
 *
 * Build: gcc -O2 -Wall -Wextra -o hazptr-bloom-test hazptr-bloom-test.c -lm
 * Run:   ./hazptr-bloom-test [seed]
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <math.h>
#include <time.h>

#define HAZPTR_BLOOM_HASHES 3
#define HAZPTR_BLOOM_NBITS  (4096UL * 8)
#define HAZPTR_BLOOM_LONGS  (4096 / sizeof(unsigned long))

_Static_assert(sizeof(unsigned long) == 8, "64-bit LP64 userspace required");
_Static_assert(sizeof(void *) == 8, "64-bit pointers required");

struct hazptr_bloom {
        unsigned long map[HAZPTR_BLOOM_LONGS];
};

static unsigned long hazptr_bloom_hash(void *addr, unsigned int i)
{
        static const uint64_t mult[HAZPTR_BLOOM_HASHES] = {
                0x9E3779B97F4A7C15ULL, 0xC2B2AE3D27D4EB4FULL,
0x165667B19E3779F9ULL,
        };

        /* 32768 bits: use the top 15 product bits. */
        return (uint64_t)(unsigned long)addr * mult[i] >> 49;
}

static void set_bit(unsigned long *map, unsigned long bit)
{
        map[bit / (sizeof(unsigned long) * 8)] |=
                1UL << (bit % (sizeof(unsigned long) * 8));
}

static bool test_bit(const unsigned long *map, unsigned long bit)
{
        return map[bit / (sizeof(unsigned long) * 8)] &
                (1UL << (bit % (sizeof(unsigned long) * 8)));
}

static void hazptr_bloom_reset(struct hazptr_bloom *bloom)
{
        memset(bloom->map, 0, sizeof(bloom->map));
}

static void hazptr_bloom_add(struct hazptr_bloom *bloom, void *addr)
{
        for (unsigned int i = 0; i < HAZPTR_BLOOM_HASHES; i++)
                set_bit(bloom->map, hazptr_bloom_hash(addr, i));
}

static bool hazptr_bloom_contains(const struct hazptr_bloom *bloom, void *addr)
{
        for (unsigned int i = 0; i < HAZPTR_BLOOM_HASHES; i++)
                if (!test_bit(bloom->map, hazptr_bloom_hash(addr, i)))
                        return false;
        return true;
}

static uint64_t prng_state = 0x9E3779B97F4A7C15ULL;

static uint64_t prng(void)
{
        prng_state ^= prng_state >> 12;
        prng_state ^= prng_state << 25;
        prng_state ^= prng_state >> 27;
        return prng_state * 0x2545F4914F6CDD1DULL;
}

static void prng_seed(uint64_t seed)
{
        prng_state = seed ? seed : 0x9E3779B97F4A7C15ULL; /* 0 would freeze */
}

struct dist {
        const char *name;
        uint64_t obs_base, query_base;  /* roots for inserted / queried sets */
        uint64_t prefix;        /* OR'd into every address */
        uint64_t stride;        /* 0 => random low 48 bits */
};

/*
 * The inserted and queried address sets must be disjoint.
 * Keep them disjoint when changing bases, strides, or query count.
 */
static void gen_addrs(const struct dist *d, uint64_t base, int n, void **out)
{
        for (int i = 0; i < n; i++)
                out[i] = (void *)(d->stride ?
                           (base | d->prefix) + (uint64_t)i * d->stride :
                           base | (prng() & 0xFFFFFFFFFFFFULL));
}

/* Synthetic x86-64 high-half address patterns. */
static const struct dist dists[] = {
        { "random",      0x1111000000000000, 0x2222000000000000, 0,
              0 },
        { "page-aligned",       0x888100000000,     0x999100000000,
 0,                  4096 },
        { "cacheline",   0x888200000000,     0x999200000000,     0,
              64 },
        { "sequential",  0x888300000000,     0x999300000000,     0,
              8 },
        { "hi-half1",    0,                  0x100000000000,
0xffff800000000000, 4096 },
        { "hi-half2",    0,                  0x200000000000,
0xffff888000000000, 4096 },
};

#define N_QUERIES 1000000
#define MAX_N     10000  /* largest entry in sizes[] */

static const int sizes[] = { 100, 256, 512, 768, 1024, 2000, 5000, 10000 };

static double fp_rate(const struct dist *d, int n, void **obs, void **queries)
{
        struct hazptr_bloom bloom;
        int fp = 0;

        gen_addrs(d, d->obs_base, n, obs);
        hazptr_bloom_reset(&bloom);
        for (int i = 0; i < n; i++)
                hazptr_bloom_add(&bloom, obs[i]);

        gen_addrs(d, d->query_base, N_QUERIES, queries);
        for (int i = 0; i < N_QUERIES; i++)
                fp += hazptr_bloom_contains(&bloom, queries[i]);

        return (double)fp / N_QUERIES;
}

/* ideal Bloom filter: (1 - e^(-kn/m))^k */
static double theory(int n)
{
        double k = HAZPTR_BLOOM_HASHES, m = HAZPTR_BLOOM_NBITS;

        return pow(1.0 - exp(-k * n / m), k);
}

int main(int argc, char **argv)
{
        int n_sizes = sizeof(sizes) / sizeof(sizes[0]);
        int n_dists = sizeof(dists) / sizeof(dists[0]);
        uint64_t seed;

        if (argc > 1) {
                char *end;

                errno = 0;
                seed = strtoull(argv[1], &end, 0);
                if (argv[1][0] == '-' || errno ||
                    end == argv[1] || *end != '\0') {
                        fprintf(stderr, "invalid seed: %s\n", argv[1]);
                        return 1;
                }
        } else {
                seed = (uint64_t)time(NULL);
        }

        void **obs = malloc(sizeof(*obs) * MAX_N);
        void **queries = malloc(sizeof(*queries) * N_QUERIES);

        if (!obs || !queries) {
                perror("malloc");
                free(obs);
                free(queries);
                return 1;
        }
        prng_seed(seed);

        printf("hazptr Bloom filter FP-rate sanity test\n");
        printf("bitmap %lu bits, k=%d, %d queries/point, seed %#llx\n\n",
               HAZPTR_BLOOM_NBITS, HAZPTR_BLOOM_HASHES, N_QUERIES,
               (unsigned long long)seed);

        printf("%-13s", "n");
        for (int j = 0; j < n_sizes; j++)
                printf("%9d", sizes[j]);
        printf("\n%-13s", "theory");
        for (int j = 0; j < n_sizes; j++)
                printf("%9.6f", theory(sizes[j]));
        printf("\n");

        for (int i = 0; i < n_dists; i++) {
                printf("%-13s", dists[i].name);
                for (int j = 0; j < n_sizes; j++)
                        printf("%9.6f",
                               fp_rate(&dists[i], sizes[j], obs, queries));
                printf("\n");
        }

        free(queries);
        free(obs);
        return 0;
}

Thanks,
Kunwu

>
> Joel
>
>
>
> >
> > Thanks,
> > Kunwu
> >
> >> Thanks!
> >>
> >> Mathieu
> >>
> >>> Signed-off-by: Kunwu Chan <[email protected]>
> >>> ---
> >>>  kernel/hazptr.c | 93 +++++++++++++++++++++++++++++++++++++++++++------
> >>>  1 file changed, 83 insertions(+), 10 deletions(-)
> >>>
> >>> diff --git a/kernel/hazptr.c b/kernel/hazptr.c
> >>> index 9e274a691af5..900ba35de2cb 100644
> >>> --- a/kernel/hazptr.c
> >>> +++ b/kernel/hazptr.c
> >>> @@ -7,6 +7,7 @@
> >>>   */
> >>>
> >>>  #include <linux/hazptr.h>
> >>> +#include <linux/bitops.h>
> >>>  #include <linux/percpu.h>
> >>>  #include <linux/spinlock.h>
> >>>  #include <linux/mutex.h>
> >>> @@ -16,6 +17,7 @@
> >>>  #include <linux/kthread.h>
> >>>  #include <linux/swait.h>
> >>>  #include <linux/sched.h>
> >>> +#include <linux/bitmap.h>
> >>>
> >>>  /*
> >>>   * The current hazard pointer wildcard. Flips between 1UL and 2UL to 
> >>> guarantee
> >>> @@ -213,6 +215,55 @@ void hazptr_scan_period(void *addr, void 
> >>> *scan_wildcard)
> >>>      }
> >>>  }
> >>>
> >>> +/* Number of hash functions for the Bloom filter. */
> >>> +#define HAZPTR_BLOOM_HASHES 3
> >>> +
> >>> +/* Number of bits in the Bloom filter bitmap: one page. */
> >>> +#define HAZPTR_BLOOM_NBITS (PAGE_SIZE * 8)
> >>> +
> >>> +struct hazptr_bloom {
> >>> +     unsigned long map[PAGE_SIZE / sizeof(unsigned long)];
> >>> +};
> >>> +
> >>> +/*
> >>> + * Multiply @addr by a per-hash odd constant and use the high bits
> >>> + * as the Bloom filter index.
> >>> + */
> >>> +static unsigned long hazptr_bloom_hash(void *addr, unsigned int i)
> >>> +{
> >>> +     static const u64 mult[HAZPTR_BLOOM_HASHES] = {
> >>> +             0x9E3779B97F4A7C15ULL,
> >>> +             0xC2B2AE3D27D4EB4FULL,
> >>> +             0x165667B19E3779F9ULL,
> >>> +     };
> >>> +     u64 hash = (u64)(unsigned long)addr * mult[i];
> >>> +
> >>> +     return hash >> (64 - ilog2(HAZPTR_BLOOM_NBITS));
> >>> +}
> >>> +
> >>> +static void hazptr_bloom_reset(struct hazptr_bloom *bloom)
> >>> +{
> >>> +     bitmap_zero(bloom->map, HAZPTR_BLOOM_NBITS);
> >>> +}
> >>> +
> >>> +static void hazptr_bloom_add(struct hazptr_bloom *bloom, void *addr)
> >>> +{
> >>> +     unsigned int i;
> >>> +
> >>> +     for (i = 0; i < HAZPTR_BLOOM_HASHES; i++)
> >>> +             __set_bit(hazptr_bloom_hash(addr, i), bloom->map);
> >>> +}
> >>> +
> >>> +static bool hazptr_bloom_contains(const struct hazptr_bloom *bloom, void 
> >>> *addr)
> >>> +{
> >>> +     unsigned int i;
> >>> +
> >>> +     for (i = 0; i < HAZPTR_BLOOM_HASHES; i++)
> >>> +             if (!test_bit(hazptr_bloom_hash(addr, i), bloom->map))
> >>> +                     return false;
> >>> +     return true;
> >>> +}
> >>> +
> >>>  struct hazptr_waiter {
> >>>      struct list_head node;
> >>>      void *addr;
> >>> @@ -226,17 +277,26 @@ struct hazptr_scan_state {
> >>>      struct mutex lock;
> >>>      struct list_head pending;
> >>>      struct list_head scanning;      /* kthread only */
> >>> +     struct hazptr_bloom bloom;      /* kthread only */
> >>>  };
> >>>  static struct hazptr_scan_state hazptr_scan;
> >>>
> >>>  /*
> >>> - * Check per-CPU slots before overflow-list slots to match the
> >>> - * acquisition ordering of promoted slots.
> >>> + * Walk all slots and return true if @watch is present.  If @bloom
> >>> + * is non-NULL, record observed non-wildcard addresses in it.
> >>> + *
> >>> + * Per-CPU slots are examined before overflow-list slots on each CPU
> >>> + * to preserve the acquisition ordering required by the promote path:
> >>> + * synchronize must observe the per-CPU slot release before the
> >>> + * overflow-list entry can be missed.
> >>>   */
> >>> -static bool hazptr_value_present(void *val)
> >>> +static bool hazptr_scan_walk(void *watch, struct hazptr_bloom *bloom)
> >>>  {
> >>>      int cpu;
> >>>
> >>> +     if (bloom)
> >>> +             hazptr_bloom_reset(bloom);
> >>> +
> >>>      for_each_possible_cpu(cpu) {
> >>>              struct hazptr_percpu_slots *percpu_slots = 
> >>> per_cpu_ptr(&hazptr_percpu_slots, cpu);
> >>>              struct hazptr_overflow_list_flip *overflow_list_flip = 
> >>> per_cpu_ptr(&percpu_overflow_list_flip, cpu);
> >>> @@ -244,10 +304,14 @@ static bool hazptr_value_present(void *val)
> >>>
> >>>              for (idx = 0; idx < NR_HAZPTR_PERCPU_SLOTS; idx++) {
> >>>                      struct hazptr_slot_item *item = 
> >>> &percpu_slots->items[idx];
> >>> +                     void *v;
> >>>
> >>>                      /* Pairs with smp_store_release in hazptr_release(). 
> >>> */
> >>> -                     if (smp_load_acquire(&item->slot.addr) == val)
> >>> +                     v = smp_load_acquire(&item->slot.addr);
> >>> +                     if (v == watch)
> >>>                              return true;
> >>> +                     if (bloom && v && !is_wildcard(v))
> >>> +                             hazptr_bloom_add(bloom, v);
> >>>              }
> >>>              for (int i = 0; i < 2; i++) {
> >>>                      struct hazptr_overflow_list *list = 
> >>> &overflow_list_flip->array[i];
> >>> @@ -256,11 +320,16 @@ static bool hazptr_value_present(void *val)
> >>>
> >>>                      raw_spin_lock_irqsave(&list->lock, flags);
> >>>                      hlist_for_each_entry(b, &list->head, overflow_node) {
> >>> +                             void *v;
> >>> +
> >>>                              /* Pairs with smp_store_release in 
> >>> hazptr_release(). */
> >>> -                             if (smp_load_acquire(&b->slot.addr) == val) 
> >>> {
> >>> +                             v = smp_load_acquire(&b->slot.addr);
> >>> +                             if (v == watch) {
> >>>                                      
> >>> raw_spin_unlock_irqrestore(&list->lock, flags);
> >>>                                      return true;
> >>>                              }
> >>> +                             if (bloom && v && !is_wildcard(v))
> >>> +                                     hazptr_bloom_add(bloom, v);
> >>>                      }
> >>>                      raw_spin_unlock_irqrestore(&list->lock, flags);
> >>>              }
> >>> @@ -276,7 +345,7 @@ static bool hazptr_value_present(void *val)
> >>>   */
> >>>  static void hazptr_drain_wildcard(void *wc)
> >>>  {
> >>> -     while (hazptr_value_present(wc))
> >>> +     while (hazptr_scan_walk(wc, NULL))
> >>>              cond_resched();
> >>>  }
> >>>
> >>> @@ -309,12 +378,16 @@ static void hazptr_scan_do_cycle(void)
> >>>      WRITE_ONCE(hazptr_wildcard, scan_wildcard);
> >>>      old_wildcard = flip_wildcard(scan_wildcard);
> >>>
> >>> -     /* Pass 2: drain the old wildcard. */
> >>> -     hazptr_drain_wildcard(old_wildcard);
> >>> +     /*
> >>> +      * Pass 2: drain the old wildcard while collecting observed
> >>> +      * addresses into the Bloom filter.
> >>> +      */
> >>> +     while (hazptr_scan_walk(old_wildcard, &hazptr_scan.bloom))
> >>> +             cond_resched();
> >>>
> >>> -     /* Complete waiters whose address is no longer held by any slot. */
> >>> +     /* Complete waiters whose address is not in the filter. */
> >>>      list_for_each_entry_safe(w, n, &hazptr_scan.scanning, node) {
> >>> -             if (!hazptr_value_present(w->addr))
> >>> +             if (!hazptr_bloom_contains(&hazptr_scan.bloom, w->addr))
> >>>                      list_move(&w->node, &done);
> >>>      }
> >>>
> >>
> >>
> >> --
> >> Mathieu Desnoyers
> >> EfficiOS Inc.
> >> https://www.efficios.com

Reply via email to