2012-06-20 17 views
7

znalazłem następujący kod Spinlock w boost::smart_ptr:różnica między pthread_spinlock i boost :: smart_ptr :: spinlock?

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_); 
} 

Więc jeśli dobrze rozumiem to poprawnie, gdy zamek jest utrzymywał wątek przychodząca będzie wykładniczo back-off, pierwszy przędzenia dziko, a następnie zatrzymując się, a następnie uzyskując pozostałą z jego fragmentu czasu, a na koniec flip-flopping pomiędzy snem a plonowaniem.

Znalazłem również implementację glibc pthread_spinlock, która używa zespołu do wykonania blokady.

#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; 
} 

Przyznam, że moje rozumienie zgromadzeń nie jest wspaniałe, więc nie w pełni rozumiem, co się tutaj dzieje. (Może ktoś proszę wyjaśnić co to robi?)

Jednak wpadłem kilka testów przeciwko doładowania Spinlock i glibc pthread_spinlock, a gdy istnieją więcej rdzeni niż nici, kod doładowania przewyższa kodu glibc.

Z drugiej strony, gdy istnieje więcej wątków niż rdzeni, kod glibc jest lepszy.

Dlaczego tak jest? Jaka jest różnica między tymi dwoma implementacjami spinlock, które powodują, że w każdym scenariuszu działają inaczej?

+0

Zabawne, zrobiłem podobny test kilka lat temu i doszedłem do tego samego wniosku: 'pthread_spin_lock' jest bardziej efektywny niż manual spinlocks (linia z boost), gdy jest dużo rywalizacji. –

Odpowiedz

5

Skąd wziąłeś implementację pthread_spin_lock() w pytaniu? Wygląda na to, że brakuje kilku ważnych linii.

Realizacja widzę (co nie jest inline montaż - to plik źródłowy montaż samodzielny od glibc/nptl/sysdeps/i386/pthread_spin_lock.S) wygląda podobnie, ale ma dwa dodatkowe instrukcje krytyczne:

#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 

To zmniejsza się long szpiczasty przez przekazany parametr i zwraca, jeśli wynik wynosi zero.

W przeciwnym razie wynik był niezerowy, co oznacza, że ​​ten wątek nie uzyskał blokady. Wykonuje więc rep nop, co jest równoważne z instrukcją pause. Jest to "specjalny" nop, który daje wskazówkę procesorowi, w którym wątek jest w spinie, a procesor powinien obsługiwać porządkowanie pamięci i/lub odgałęzienia firmy w jakiś sposób, który poprawia wydajność w tych sytuacjach (nie udaje mi się dokładnie zrozumieć, co dzieje się inaczej pod osłonami układu - z punktu widzenia oprogramowania, nie ma różnicy od zwykłego starego nop).

Po pause ponownie sprawdza wartość - jeśli jest większa od zera, blokada nie zostanie odebrana, więc przeskakuje na początek funkcji i próbuje ponownie odebrać blokadę. W przeciwnym razie ponownie przeskakuje do pause.

Główną różnicą między tym spinlock i wersji Boost, jest to, że nigdy nie robi nic bardziej wyszukane niż pause kiedy to kręci - nie ma to jak sched_yield() lub nanosleep(). Wątek pozostaje gorący. Nie jestem pewien dokładnie, jak to się dzieje w dwóch zauważonych przez ciebie zachowaniach, ale kod glibc będzie bardziej chciwy - jeśli wątek obraca się na zamku i są inne wątki gotowe do uruchomienia, ale nie ma dostępnego rdzenia, wirujący wątek nie działa ". • Pomóż oczekującej wątku uzyskać dowolny czas procesora, podczas gdy wersja "Zwiększ" w końcu dobrowolnie utoruje drogę wątkowi, który czeka na jakąś uwagę.

+0

Znalazłem go gdzieś online - niestety nie pamiętam dokładnie, gdzie - użyłem pthread_spin_lock w moich testach, dowiedziałem się o wynikach, które zgłosiłem, i kiedy zobaczyłem różnice, poszedłem po kod źródłowy, aby spróbować zrozumieć, co się dzieje , stwierdziłem, że zgromadzenie, a kiedy nie mogłem zrozumieć, przyszło szukać tu pomocy. Dzięki za odpowiedź i wyjaśnienie! –

+0

Zastanawiam się, Wydaje mi się to sprzeczne z intuicją - bardziej żarłoczna implementacja pthreada, która nie poddaje się sednie pod rywalizacją, działa lepiej, gdy rdzenie są za dużo subskrybowane (więcej wątków niż rdzeni) - oczekiwałbym, że implementacja doładowania będzie lepsza w tym scenariuszu. –

+0

@lori: trudno powiedzieć, co się dzieje - nie mamy żadnych informacji na temat benchmarków. To powiedziawszy, nie jestem pewien, jak często używanie spinlocków ma sens w kodzie trybu użytkownika. Nawet jeśli robili to czasami, myślę, że powinny być używane tylko wtedy, gdy można się spodziewać niskiej rywalizacji i że będą one prowadzone tak krótko, jak to możliwe. Wykonywanie rodzajów kontroli, które zwiększają, może być trochę zbytnie. Z drugiej strony, to tylko opinia oparta na uczuciu, a nie danych. –

Powiązane problemy