Re: [PATCH v6 1/4] rcu: Make call_rcu() lazy to save power

Joel Fernandes
2022-09-25T01:00:48Z

> On Sep 24, 2022, at 7:28 PM, Joel Fernandes <joel@joelfernandes.org> wrote:
>
> ﻿Hi Frederic, thanks for the response, replies
> below courtesy fruit company’s device:
>
>>> On Sep 24, 2022, at 6:46 PM, Frederic Weisbecker <frederic@kernel.org> wrote:
>>>
>>> ﻿On Thu, Sep 22, 2022 at 10:01:01PM +0000, Joel Fernandes (Google) wrote:
>>> @@ -3902,7 +3939,11 @@ static void rcu_barrier_entrain(struct rcu_data *rdp)
>>> rdp->barrier_head.func = rcu_barrier_callback;
>>> debug_rcu_head_queue(&rdp->barrier_head);
>>> rcu_nocb_lock(rdp);
>>> - WARN_ON_ONCE(!rcu_nocb_flush_bypass(rdp, NULL, jiffies));
>>> + /*
>>> + * Flush the bypass list, but also wake up the GP thread as otherwise
>>> + * bypass/lazy CBs maynot be noticed, and can cause real long delays!
>>> + */
>>> + WARN_ON_ONCE(!rcu_nocb_flush_bypass(rdp, NULL, jiffies, FLUSH_BP_WAKE));
>>
>> This fixes an issue that goes beyond lazy implementation. It should be done
>> in a separate patch, handling rcu_segcblist_entrain() as well, with "Fixes: " tag.
>
> I wanted to do that, however on discussion with
> Paul I thought of making this optimization only for
> all lazy bypass CBs. That makes it directly related
> this patch since the laziness notion is first
> introduced here. On the other hand I could make
> this change in a later patch since we are not
> super bisectable anyway courtesy of the last
> patch (which is not really an issue if the CONFIG
> is kept off during someone’s bisection.

Or are we saying it’s worth doing the wake up for rcu barrier even for regular bypass CB? That’d save 2 jiffies on rcu barrier. If we agree it’s needed, then yes splitting the patch makes sense.

Please let me know your opinions, thanks,

- Joel

>
>> And then FLUSH_BP_WAKE is probably not needed anymore.
>
> It is needed as the API is in tree_nocb.h and we
> have to have that handle the details of laziness
> there rather than tree.c. We could add new apis
> to get rid of flag but it’s cleaner (and Paul seemed
> to be ok with it).
>
>>> if (rcu_segcblist_entrain(&rdp->cblist, &rdp->barrier_head)) {
>>> atomic_inc(&rcu_state.barrier_cpu_count);
>>> } else {
>>> @@ -269,10 +294,14 @@ static void wake_nocb_gp_defer(struct rcu_data *rdp, int waketype,
>>> raw_spin_lock_irqsave(&rdp_gp->nocb_gp_lock, flags);
>>>
>>> /*
>>> - * Bypass wakeup overrides previous deferments. In case
>>> - * of callback storm, no need to wake up too early.
>>> + * Bypass wakeup overrides previous deferments. In case of
>>> + * callback storm, no need to wake up too early.
>>> */
>>> - if (waketype == RCU_NOCB_WAKE_BYPASS) {
>>> + if (waketype == RCU_NOCB_WAKE_LAZY
>>> + && READ_ONCE(rdp->nocb_defer_wakeup) == RCU_NOCB_WAKE_NOT) {
>>
>> This can be a plain READ since ->nocb_defer_wakeup is only written under ->nocb_gp_lock.
>
> Yes makes sense, will do.
>
>>> + mod_timer(&rdp_gp->nocb_timer, jiffies + jiffies_till_flush);
>>> + WRITE_ONCE(rdp_gp->nocb_defer_wakeup, waketype);
>>> + } else if (waketype == RCU_NOCB_WAKE_BYPASS) {
>>> mod_timer(&rdp_gp->nocb_timer, jiffies + 2);
>>> WRITE_ONCE(rdp_gp->nocb_defer_wakeup, waketype);
>>> } else {
>>> @@ -512,9 +598,16 @@ static void __call_rcu_nocb_wake(struct rcu_data *rdp, bool was_alldone,
>>> }
>>> // Need to actually to a wakeup.
>>> len = rcu_segcblist_n_cbs(&rdp->cblist);
>>> + bypass_len = rcu_cblist_n_cbs(&rdp->nocb_bypass);
>>> + lazy_len = READ_ONCE(rdp->lazy_len);
>>> if (was_alldone) {
>>> rdp->qlen_last_fqs_check = len;
>>> - if (!irqs_disabled_flags(flags)) {
>>> + // Only lazy CBs in bypass list
>>> + if (lazy_len && bypass_len == lazy_len) {
>>> + rcu_nocb_unlock_irqrestore(rdp, flags);
>>> + wake_nocb_gp_defer(rdp, RCU_NOCB_WAKE_LAZY,
>>> + TPS("WakeLazy"));
>>
>> I'm trying to think of a case where rcu_nocb_try_bypass() returns false
>> (queue to regular list) but then call_rcu() -> __call_rcu_nocb_wake() ends up
>> seeing a lazy bypass queue even though we are queueing a non-lazy callback
>> (should have flushed in this case).
>>
>> Looks like it shouldn't happen, even with concurrent (de-offloading) but just
>> in case, can we add:
>
> Yes I also feel this couldn’t happen because irq is
> off and nocb lock is held throughout the calls to
> the above 2 functions. Unless I missed the race
> you’re describing?
>
>>
>> WARN_ON_ONCE(lazy_len != len)
>
> But this condition can be true even in normal
> circumstances? len also contains DONE CBs
> which are ready to be invoked. Or did I miss
> something?
>
> Thanks,
>
> - Joel
>
>>
>>> + } else if (!irqs_disabled_flags(flags)) {
>>> /* ... if queue was empty ... */
>>> rcu_nocb_unlock_irqrestore(rdp, flags);
>>> wake_nocb_gp(rdp, false);
>>
>> Thanks.