2011-12-12 13 views
50

W Dmitrija Vyukov wspaniałe ograniczony MPMC kolejce napisany w C++ zobacz: http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queueJak i kiedy wyrównać do rozmiaru linii pamięci podręcznej?

Dodaje kilka zmiennych padding. Zakładam, że jest to wyrównanie do linii pamięci podręcznej dla wydajności.

Mam kilka pytań.

  1. Dlaczego robi się to w ten sposób?
  2. Czy jest to przenośna metoda, która będzie zawsze działać zawsze działa
  3. W jakich przypadkach najlepiej byłoby użyć __attribute__ ((aligned (64))) zamiast tego.
  4. dlaczego podszewka przed wskaźnikiem buforu pomaga w wydajności? to nie tylko wskaźnik załadowany do pamięci podręcznej, więc tak naprawdę jest to tylko rozmiar wskaźnika?

    static size_t const  cacheline_size = 64; 
    typedef char   cacheline_pad_t [cacheline_size]; 
    
    cacheline_pad_t   pad0_; 
    cell_t* const   buffer_; 
    size_t const   buffer_mask_; 
    cacheline_pad_t   pad1_; 
    std::atomic<size_t>  enqueue_pos_; 
    cacheline_pad_t   pad2_; 
    std::atomic<size_t>  dequeue_pos_; 
    cacheline_pad_t   pad3_; 
    

to będzie działać pojęcie pod gcc dla kodu C?

Odpowiedz

34

Dokonano tego w ten sposób, że różne rdzenie modyfikujące różne pola nie będą musiały odbijać linii pamięci podręcznej zawierającej oba z nich między ich pamięciami podręcznymi. Ogólnie rzecz biorąc, aby procesor mógł uzyskać dostęp do niektórych danych w pamięci, cała zawarta w nim pamięć podręczna musi znajdować się w lokalnej pamięci podręcznej tego procesora. Jeśli modyfikuje on te dane, ten wpis pamięci podręcznej zwykle musi być jedyną kopią w dowolnej pamięci podręcznej w systemie (tryb wyłączności w protokołach spójności pamięci podręcznej MESI/MOESI ). Gdy oddzielne rdzenie próbują modyfikować różne dane, które zdarzają się żyć na tej samej linii pamięci podręcznej, a tym samym marnować czas na przesuwanie całej linii tam iz powrotem, to jest znane jako fałszywe udostępnianie.

W konkretnym przykładzie dajesz, jeden rdzeń może być skolejkowania wpisu (czytanie (wspólny) buffer_ i piśmie (wyłączny) tylko enqueue_pos_), podczas gdy inny dequeues (wspólna buffer_ i wyłączne dequeue_pos_) bez obu rdzenia zwłokę na linii pamięci podręcznej własnością drugiego.

Wyściełanie na początku oznacza, że ​​buffer_ i buffer_mask_ kończy się na tej samej linii pamięci podręcznej, zamiast podzielić na dwie linie, a zatem wymaga podwójnego ruchu pamięci, aby uzyskać dostęp.

Nie jestem pewien, czy technika jest całkowicie przenośna. Założeniem jest, że każdy cacheline_pad_t sam dopasuje się do 64-bajtowej (rozmiaru) granicy linii pamięci podręcznej, a co za tym idzie, będzie na następnej linii pamięci podręcznej. O ile mi wiadomo, standardy językowe C i C++ wymagają tylko tych całych struktur, aby mogły ładnie łączyć się z tablicami, bez naruszania wymagań związanych z dopasowaniem któregokolwiek z ich członków. (patrz komentarze)

Podejście attribute byłoby bardziej specyficzne dla kompilatora, ale mogłoby zmniejszyć rozmiar tej struktury o połowę, ponieważ dopełnienie byłoby ograniczone do zaokrąglenia każdego elementu do pełnej linii pamięci podręcznej. To może być całkiem korzystne, jeśli ktoś ma ich dużo.

Ta sama koncepcja dotyczy zarówno języka C, jak i C++.

+0

@Novelcrat - OK, to ma wiele sensu. A co z pytaniami 2 i 3? – Matt

+9

@MattH: Dla przenośności C++ 11 wprowadza 'std :: matching_storage', który pozwala wymagać przechowywania określonego rozmiaru i wyrównania.Domyślnym wyrównaniem dla 'char [N]' jest "1" w przeciwnym razie. –

+1

Dlaczego linker nie optymalizuje wyściełanych zmiennych, jeśli nie są używane? – RishiD

Powiązane problemy