7

で次のスピンロック コードを見つけましたboost::smart_ptr

bool try_lock()
{
    return (__sync_lock_test_and_set(&v_, 1) == 0);
}
void lock()
{    
    for (unsigned k=0; !try_lock(); ++k)
    {
        if (k<4)
            ; // spin
        else if (k < 16)
            __asm__ __volatile__("pause"); // was ("rep; nop" ::: "memory")
        else if (k < 32 || k & 1)
            sched_yield();
        else
        {
            struct timespec rqtp;
            rqtp.tv_sec  = 0;
            rqtp.tv_nsec = 100;
            nanosleep(&rqtp, 0);
        }
    }
}
void unlock()
{
     __sync_lock_release(&v_);
}

したがって、これを正しく理解していれば、ロックが競合すると、着信スレッドは指数関数的にバックオフし、最初に乱暴にスピンし、次に一時停止し、タイム スライスの残りを明け渡し、最後にスリープと明け渡しの間でフリップ フロップします。

glibc pthread_spinlockまた、アセンブリを使用してロックを実行する実装も見つけました。

#define LOCK_PREFIX "lock;" // using an SMP machine

int pthread_spin_lock(pthread_spinlock_t *lock)
{
    __asm__ ("\n"
       "1:\t" LOCK_PREFIX "decl %0\n\t"
       "jne 2f\n\t"
       ".subsection 1\n\t"
       ".align 16\n"
       "2:\trep; nop\n\t"
       "cmpl $0, %0\n\t"
       "jg 1b\n\t"
       "jmp 2b\n\t"
       ".previous"
       : "=m" (*lock)
       : "m" (*lock));

    return 0;
}

アセンブリについての私の理解が不十分であることは認めます。そのため、ここで何が起こっているのかを完全には理解していません。(誰かがこれが何をしているのか説明してもらえますか?)

ただし、ブースト スピンロックと glibc pthread_spinlock に対していくつかのテストを実行したところ、スレッドよりもコアが多い場合、ブースト コードは glibc コードよりも優れたパフォーマンスを示しました

一方、コアよりもスレッドが多い場合は、glibc コードの方が優れています。

どうしてこれなの?これら 2 つのスピンロックの実装の違いは、シナリオごとに異なるパフォーマンスをもたらすことは何ですか?

4

1 に答える 1

5

pthread_spin_lock()質問に投稿された実装をどこで入手しましたか? いくつかの重要な行が欠落しているようです。

私が見た実装 (これはインライン アセンブリではなく、 のスタンドアロン アセンブリ ソース ファイルですglibc/nptl/sysdeps/i386/pthread_spin_lock.S) は似ていますが、次の 2 つの重要な命令が追加されています。

#include <lowlevellock.h>

    .globl  pthread_spin_lock
    .type   pthread_spin_lock,@function
    .align  16
pthread_spin_lock:
    mov 4(%esp), %eax
1:  LOCK
    decl    0(%eax)
    jne 2f
    xor %eax, %eax
    ret

    .align  16
2:  rep
    nop
    cmpl    $0, 0(%eax)
    jg  1b
    jmp 2b
    .size   pthread_spin_lock,.-pthread_spin_lock

渡されたパラメーターが指すa をデクリメントしlong、結果がゼロの場合は戻ります。

それ以外の場合、結果はゼロ以外でした。これは、このスレッドがロックを取得しなかったことを意味します。したがってrep nop、命令と同等の を実行しpauseます。これは、スレッドがスピン中であるというヒントを CPU に与える「特別な」nop であり、CPU は、これらの状況でパフォーマンスを向上させる何らかの方法でメモリの順序付けおよび/または分岐予測を処理する必要があります (私はふりをしません)。チップのカバーの下で何が違うのかを正確に理解してください - ソフトウェアの観点からは、普通の古いものと違いはありませんnop)。

値を再度チェックした後、pause値がゼロより大きい場合はロックが要求されていないため、関数の先頭にジャンプしてロックを再度要求しようとします。それ以外の場合は、後ろにジャンプしpauseます。

このスピンロックと Boost バージョンの主な違いは、これがpause回転しているときに a よりも手の込んだことを決してしないことです。 asched_yield()またはのようなものはありませんnanosleep()。だから糸は熱いままです。これがあなたが指摘した2つの動作でどのように機能するか正確にはわかりませんが、glibcコードはより貪欲になります-スレッドがロックで回転していて、実行する準備ができている他のスレッドがあるが利用可能なコアがない場合、回転しているスレッドはそうしません'待っているスレッドがCPU時間を取得するのを助けませんが、Boostバージョンは最終的に、何らかの注意を待っているスレッドに自発的に道を譲ります。

于 2012-07-13T00:57:07.107 に答える