Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 2 Aug 2021 05:43:57 +0200
From:      Mateusz Guzik <mjguzik@gmail.com>
To:        Alexander Motin <mav@freebsd.org>
Cc:        src-committers@freebsd.org, dev-commits-src-all@freebsd.org,  dev-commits-src-main@freebsd.org
Subject:   Re: git: 8bb173fb5bc3 - main - sched_ule(4): Use trylock when stealing load.
Message-ID:  <CAGudoHEeOZ8aSijNnyU3UU4Cb2BfTXoYiM%2BBHD-A1JgnU_OD0A@mail.gmail.com>
In-Reply-To: <202108020258.1722walG094152@gitrepo.freebsd.org>
References:  <202108020258.1722walG094152@gitrepo.freebsd.org>

next in thread | previous in thread | raw e-mail | index | archive | help
Mostly related, I just had a look at the code and noticed:

static uint32_t
sched_random(void)
{
        uint32_t *rndptr;

        rndptr = DPCPU_PTR(randomval);
        *rndptr = *rndptr * 69069 + 5;

        return (*rndptr >> 16);
}

Except randomval starts at 0 for all CPUs. Should be easy to pre-init
with an actual random value. Is that something you played with?

On 8/2/21, Alexander Motin <mav@freebsd.org> wrote:
> The branch main has been updated by mav:
>
> URL:
> https://cgit.FreeBSD.org/src/commit/?id=8bb173fb5bc33a02d5a4670c9a60bba0ece07bac
>
> commit 8bb173fb5bc33a02d5a4670c9a60bba0ece07bac
> Author:     Alexander Motin <mav@FreeBSD.org>
> AuthorDate: 2021-08-02 02:42:01 +0000
> Commit:     Alexander Motin <mav@FreeBSD.org>
> CommitDate: 2021-08-02 02:42:01 +0000
>
>     sched_ule(4): Use trylock when stealing load.
>
>     On some load patterns it is possible for several CPUs to try steal
>     thread from the same CPU despite randomization introduced.  It may
>     cause significant lock contention when holding one queue lock idle
>     thread tries to acquire another one.  Use of trylock on the remote
>     queue allows both reduce the contention and handle lock ordering
>     easier.  If we can't get lock inside tdq_trysteal() we just return,
>     allowing tdq_idled() handle it.  If it happens in tdq_idled(), then
>     we repeat search for load skipping this CPU.
>
>     On 2-socket 80-thread Xeon system I am observing dramatic reduction
>     of the lock spinning time when doing random uncached 4KB reads from
>     12 ZVOLs, while IOPS increase from 327K to 403K.
>
>     MFC after:      1 month
> ---
>  sys/kern/sched_ule.c | 39 +++++++++++++++++++++++++--------------
>  1 file changed, 25 insertions(+), 14 deletions(-)
>
> diff --git a/sys/kern/sched_ule.c b/sys/kern/sched_ule.c
> index 028e07efa889..1bdcfb1f793d 100644
> --- a/sys/kern/sched_ule.c
> +++ b/sys/kern/sched_ule.c
> @@ -300,6 +300,8 @@ static struct tdq	tdq_cpu;
>  #define	TDQ_LOCK_ASSERT(t, type)	mtx_assert(TDQ_LOCKPTR((t)), (type))
>  #define	TDQ_LOCK(t)		mtx_lock_spin(TDQ_LOCKPTR((t)))
>  #define	TDQ_LOCK_FLAGS(t, f)	mtx_lock_spin_flags(TDQ_LOCKPTR((t)), (f))
> +#define	TDQ_TRYLOCK(t)		mtx_trylock_spin(TDQ_LOCKPTR((t)))
> +#define	TDQ_TRYLOCK_FLAGS(t, f)	mtx_trylock_spin_flags(TDQ_LOCKPTR((t)),
> (f))
>  #define	TDQ_UNLOCK(t)		mtx_unlock_spin(TDQ_LOCKPTR((t)))
>  #define	TDQ_LOCKPTR(t)		((struct mtx *)(&(t)->tdq_lock))
>
> @@ -989,13 +991,22 @@ tdq_idled(struct tdq *tdq)
>  		if (steal->tdq_load < steal_thresh ||
>  		    steal->tdq_transferable == 0)
>  			goto restart;
> -		tdq_lock_pair(tdq, steal);
>  		/*
> -		 * We were assigned a thread while waiting for the locks.
> -		 * Switch to it now instead of stealing a thread.
> +		 * Try to lock both queues. If we are assigned a thread while
> +		 * waited for the lock, switch to it now instead of stealing.
> +		 * If we can't get the lock, then somebody likely got there
> +		 * first so continue searching.
>  		 */
> -		if (tdq->tdq_load)
> -			break;
> +		TDQ_LOCK(tdq);
> +		if (tdq->tdq_load > 0) {
> +			mi_switch(SW_VOL | SWT_IDLE);
> +			return (0);
> +		}
> +		if (TDQ_TRYLOCK_FLAGS(steal, MTX_DUPOK) == 0) {
> +			TDQ_UNLOCK(tdq);
> +			CPU_CLR(cpu, &mask);
> +			continue;
> +		}
>  		/*
>  		 * The data returned by sched_highest() is stale and
>  		 * the chosen CPU no longer has an eligible thread, or
> @@ -1948,18 +1959,18 @@ tdq_trysteal(struct tdq *tdq)
>  		if (steal->tdq_load < steal_thresh ||
>  		    steal->tdq_transferable == 0)
>  			continue;
> -		tdq_lock_pair(tdq, steal);
>  		/*
> -		 * If we get to this point, unconditonally exit the loop
> -		 * to bound the time spent in the critcal section.
> -		 *
> -		 * If a thread was added while interrupts were disabled don't
> -		 * steal one here.
> +		 * Try to lock both queues. If we are assigned a thread while
> +		 * waited for the lock, switch to it now instead of stealing.
> +		 * If we can't get the lock, then somebody likely got there
> +		 * first.  At this point unconditonally exit the loop to
> +		 * bound the time spent in the critcal section.
>  		 */
> -		if (tdq->tdq_load > 0) {
> -			TDQ_UNLOCK(steal);
> +		TDQ_LOCK(tdq);
> +		if (tdq->tdq_load > 0)
> +			break;
> +		if (TDQ_TRYLOCK_FLAGS(steal, MTX_DUPOK) == 0)
>  			break;
> -		}
>  		/*
>  		 * The data returned by sched_highest() is stale and
>                   * the chosen CPU no longer has an eligible thread.
>


-- 
Mateusz Guzik <mjguzik gmail.com>



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?CAGudoHEeOZ8aSijNnyU3UU4Cb2BfTXoYiM%2BBHD-A1JgnU_OD0A>