Re: [PATCH 5/9] bcache: reduce gc latency by processing less nodes and sleep less time
Robert Pang <[email protected]> Tue, 21 Jul 2026 11:55:53 -0700
| Newsgroups | org.kernel.vger.linux-bcache |
|---|---|
| Message-ID | <CAJhEC07z6EmL42encTkdBsEhuXphwW08XkcrpdZseL4tx=B5mw@mail.gmail.com> |
On Fri, Jul 17, 2026 at 9:20=E2=80=AFPM Coly Li <[email protected]> wrote: > > > 2026=E5=B9=B47=E6=9C=8817=E6=97=A5 08:50=EF=BC=8CRobert Pang <robertpan= [email protected]> =E5=86=99=E9=81=93=EF=BC=9A > > > > Hi Coly, > > > > I was reviewing this patch again and noticed that btree_gc_min_nodes() = reads the > > atomic counter 'search_inflight'. This counter is incremented and decre= mented by > > clients issuing front-side I/O. In this scenario, should we add a memor= y barrier > > (such as `smp_mb__before_atomic`) prior to `atomic_read()`? > > > > My concern is that if btree_gc_min_nodes() is inlined in the caller > > btree_gc_recurse(), the compiler might hoist the `atomic_read()` outsid= e of the > > while loop where btree_gc_min_nodes() is inlined, preventing us from fe= tching the > > latest counter value. Adding the barrier would guarantee we read the up= dated > > value. > > > > What are your thoughts on this? > > Hi Robert, > > At the first glance I feel the code was fine. But, it was almost 8 months= ago, to > make sure I understand you correctly, can you place your comments with th= e exact > code together, then let me response you more accurately. > > Thanks for the review. > > Coly Li Hi Coly My apology for missing the code context in the earlier email. Here are the code snippets where my comments relate: diff --git a/drivers/md/bcache/btree.c b/drivers/md/bcache/btree.c index 210b59007d98..5d922d301ab6 100644 --- a/drivers/md/bcache/btree.c +++ b/drivers/md/bcache/btree.c @@ -1578,29 +1579,29 @@ static unsigned int btree_gc_count_keys(struct btre= e *b) static size_t btree_gc_min_nodes(struct cache_set *c) { - size_t min_nodes; + size_t min_nodes =3D GC_NODES_MIN; - /* - * Since incremental GC would stop 100ms when front - * side I/O comes, so when there are many btree nodes, - * if GC only processes constant (100) nodes each time, - * GC would last a long time, and the front side I/Os - * would run out of the buckets (since no new bucket - * can be allocated during GC), and be blocked again. - * So GC should not process constant nodes, but varied - * nodes according to the number of btree nodes, which - * realized by dividing GC into constant(100) times, - * so when there are many btree nodes, GC can process - * more nodes each time, otherwise, GC will process less - * nodes each time (but no less than MIN_GC_NODES) - */ - min_nodes =3D c->gc_stats.nodes / MAX_GC_TIMES; - if (min_nodes < MIN_GC_NODES) - min_nodes =3D MIN_GC_NODES; + if (atomic_read(&c->search_inflight) =3D=3D 0) { + size_t n =3D c->gc_stats.nodes >> MAX_GC_TIMES_SHIFT; + + if (min_nodes < n) + min_nodes =3D n; + } return min_nodes; } In the above code change of this patch, btree_gc_min_nodes() reads the atomic counter 'search_inflight'. And this counter is incremented and decremented by clients issuing front-side I/O below: drivers/md/bcache/request.c: static CLOSURE_CALLBACK(search_free) { closure_type(s, struct search, cl); atomic_dec(&s->iop.c->search_inflight); if (s->iop.bio) bio_put(s->iop.bio); bio_complete(s); closure_debug_destroy(cl); mempool_free(s, &s->iop.c->search); } static inline struct search *search_alloc(struct bio *bio, struct bcache_device *d, struct block_device *orig_bdev, unsigned long start_time) { struct search *s; s =3D mempool_alloc(&d->c->search, GFP_NOIO); closure_init(&s->cl, NULL); do_bio_hook(s, bio, request_endio); atomic_inc(&d->c->search_inflight); ... } My concern is that if btree_gc_min_nodes() is inlined in the caller btree_gc_recurse() below, the compiler might hoist the `atomic_read()` outside of the while loop where btree_gc_min_nodes() is inlined, preventing us from fetching the latest counter value after each btree node is traversed. drivers/md/bcache/btree.c: static int btree_gc_recurse(struct btree *b, struct btree_op *op, struct closure *writes, struct gc_stat *gc) { int ret =3D 0; bool should_rewrite; struct bkey *k; struct btree_iter_stack iter; struct gc_merge_info r[GC_MERGE_NODES]; struct gc_merge_info *i, *last =3D r + ARRAY_SIZE(r) - 1; bch_btree_iter_stack_init(&b->keys, &iter, &b->c->gc_done); for (i =3D r; i < r + ARRAY_SIZE(r); i++) i->b =3D ERR_PTR(-EINTR); while (1) { k =3D bch_btree_iter_next_filter(&iter.iter, &b->keys, bch_ptr_bad); ... if (gc->nodes >=3D (gc->nodes_pre + btree_gc_min_nodes(b->c))) { gc->nodes_pre =3D gc->nodes; ret =3D -EAGAIN; break; } .. } In this scenario, should we add a memory barrier (such as `smp_mb__before_atomic`) prior to `atomic_read()` to guarantee we read the updated value? I.e. diff --git a/drivers/md/bcache/btree.c b/drivers/md/bcache/btree.c index 377052cbde5c..659b8706031a 100644 --- a/drivers/md/bcache/btree.c +++ b/drivers/md/bcache/btree.c @@ -1582,6 +1582,8 @@ static size_t btree_gc_min_nodes(struct cache_set *c) { size_t min_nodes =3D GC_NODES_MIN; + /* Fetch latest search_inflight count */ + smp_mb__before_atomic(); if (atomic_read(&c->search_inflight) =3D=3D 0) { size_t n =3D c->gc_stats.nodes >> MAX_GC_TIMES_SHIFT; What are your thoughts on this? Best regards Robert Pang