NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

port-arm/57437: arm/aarch64: pthread_mutex is not efficient on spinlock



>Number:         57437
>Category:       port-arm
>Synopsis:       arm/aarch64: pthread_mutex is not efficient on spinlock
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    port-arm-maintainer
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Thu May 25 07:45:00 +0000 2023
>Originator:     Vasily Dybala
>Release:        all versions
>Organization:
OOO Kaspersky
>Environment:
>Description:
pthread_mutex_lock - tries to use some optimizations before goes sleep to kernel.

It just use short spinlock in hope that mutex was locked for a short time and another thread can capture it faster than switch to kernel and return back to userspace.

It is ok, such optimization is used by many implementations on different architectures and operating systems.

Lets dig into spinlock implementation from: 
http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libpthread/pthread_mutex.c?annotate=1.83&only_with_tag=MAIN

NOINLINE static void *
                    254: pthread__mutex_spin(pthread_mutex_t *ptm, pthread_t owner)
                    255: {
                    256:        pthread_t thread;
                    257:        unsigned int count, i;
                    258:
                    259:        for (count = 2;; owner = ptm->ptm_owner) {
                    260:                thread = (pthread_t)MUTEX_OWNER(owner);
                    261:                if (thread == NULL)
                    262:                        break;
1.66      ad        263:                if (thread->pt_lwpctl->lc_curcpu == LWPCTL_CPU_NONE)
1.44      ad        264:                        break;
1.83    ! riastrad  265:                if (count < 128)
1.44      ad        266:                        count += count;
                    267:                for (i = count; i != 0; i--)
                    268:                        pthread__mutex_pause();
                    269:        }
1.2       thorpej   270:
1.44      ad        271:        return owner;
                    272: }

look at lines 265-266 and loop at line 267-268.

this means that if we unable to capture mutex, we do loop over mutex_pause and if we are unable for a long time, we will do loop for more and more iterations (2, 4, 8, 16, ...)

On the most architectures is is ok, because we "sleep" on NOP instruction, so we wait for some cpu ticks and try to check mutex state again.

But on arm/aarch64 architectures it works in completely different way:
pthread__mutex_pause() is expanded to WFE instruction
http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libpthread/arch/aarch64/pthread_md.h?annotate=1.1&only_with_tag=MAIN

which have different meaning. It will go sleep cpu core till somebody send SEV instruction.

Let's look at loop again:

                    267:                for (i = count; i != 0; i--)
                    268:                        pthread__mutex_pause();

so we will sleep at WFE instruction 2,4,8,16 times, and wait next mutex status check after somebody 2,4,8 send SEV instruction (2,4,8 times send mutex__smt_wake().

It is completely wrong. So we need to check mutex status after each smt_wake() call. 

Another issue is WFE can "sleep" for infinity time, it broke behavior when we need to capture mutex during some timeout. 

>How-To-Repeat:
1) create main thread with locking some job:

pthread_mutex_lock()

for (int i = 1; i < 100000; i++)
  do NOP instruction without sleep in kernel

pthread_mutex_unlock()


2) launch other threads with the same implementation.
pthread_mutex_lock()

for (int i = 1; i < 100000; i++)
  do NOP instruction without sleep in kernel

pthread_mutex_unlock()


3) check that all of threads are in userspace and nobody will sleep in kernel
>Fix:
My suggestion is replace pthread__smt_pause/wake() for arm/aarch64 to NOP instruction.



Home | Main Index | Thread Index | Old Index