Co Każdy Programista Powinien Wiedzieć O Pamięci?

Zastanawiam się, ile z Ulricha Dreppera to, co każdy programista powinien wiedzieć o pamięci z 2007 roku, jest nadal aktualne. Nie mogłem również znaleźć nowszej wersji niż 1.0 lub errata.

Author: Peter Cordes, 2011-11-14

3 answers

Z tego co pamiętam zawartość Dreppera opisuje podstawowe pojęcia dotyczące pamięci: Jak działa pamięć podręczna procesora, czym jest pamięć fizyczna i wirtualna oraz jak radzi sobie jądro Linuksa. Prawdopodobnie w niektórych przykładach istnieją nieaktualne odniesienia do API, ale to nie ma znaczenia; nie wpłynie to na znaczenie podstawowych pojęć.

Tak więc, każda książka lub artykuł, który opisuje coś fundamentalnego nie może być nazwany przestarzały. "Co każdy programista powinien wiedzieć o pamięci" jest zdecydowanie warte do czytania, ale nie sądzę, żeby to było dla "każdego programisty". Jest bardziej odpowiedni dla facetów z systemem/osadzonym/jądrem.

 78
Author: Dan Kruchinin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-24 17:22:06

Z mojego szybkiego spojrzenia-przez to wygląda dość dokładnie. Jedną rzeczą do zauważenia jest część na temat różnicy między" zintegrowanymi "i" zewnętrznymi " kontrolerami pamięci. Od czasu wydania procesorów Intel z linii i7 wszystkie są zintegrowane, A AMD używa zintegrowanych kontrolerów pamięci od momentu wydania chipów AMD64.

Odkąd ten artykuł został napisany, niewiele się zmieniło, prędkości stały się wyższe, kontrolery pamięci stały się znacznie bardziej inteligentne (i7 opóźni zapis do pamięci RAM, dopóki nie poczuje się jak popełnienie zmian), ale niewiele się zmieniło. Przynajmniej nie w żaden sposób, na którym programista by się troszczył.

 65
Author: Timothy Baldridge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-14 18:40:52

Poradnik w formacie PDF znajduje się pod adresem https://www.akkadia.org/drepper/cpumemory.pdf .

Jest nadal ogólnie znakomity i wysoce polecany (przeze mnie, i myślę, że przez innych ekspertów od tuningu wydajności). Byłoby fajnie, gdyby Ulrich (lub ktokolwiek inny) napisał aktualizację 2017, ale byłoby to dużo pracy (np. ponowne uruchomienie benchmarków). Zobacz także inne linki do optymalizacji wydajności x86 i SSE/asm (oraz C/C++) w x86 tag wiki. (Artykuł Ulricha nie jest specyficzny dla x86, ale większość (wszystkich) jego benchmarków dotyczy sprzętu x86.)

Niskopoziomowe szczegóły sprzętowe dotyczące działania pamięci DRAM i pamięci podręcznych nadal obowiązują . DDR4 używa tych samych poleceń, jak opisano dla DDR1/DDR2 (Odczyt/Zapis burst). Ulepszenia DDR3 / 4 nie są podstawowymi zmianami. AFAIK, wszystkie rzeczy niezależne od arch nadal mają ogólne zastosowanie, np. do AArch64 / ARM32.

Patrz również sekcja Platform związanych z opóźnieniami tej odpowiedzi dla ważnych szczegółów na temat wpływu opóźnienia pamięci / L3 na przepustowość jednowątkową: bandwidth <= max_concurrency / latency, i jest to w rzeczywistości główne wąskie gardło dla przepustowości jednowątkowej na nowoczesnym wielordzeniowym procesorze, takim jak Xeon. (Ale czterordzeniowy pulpit Skylake może zbliżyć się do maksymalizacji przepustowości pamięci DRAM za pomocą jednego wątku). Ten link ma bardzo dobre informacje o sklepach NT vs. normalnych sklepach na x86.

Tak więc sugestia Ulricha w 6.5.8 wykorzystująca całą szerokość pasma (poprzez wykorzystanie zdalnej pamięci na innych węzłach NUMA, jak również własnych) jest przeciwny do wydajności na nowoczesnym sprzęcie, gdzie kontrolery pamięci mają większą przepustowość niż pojedynczy rdzeń może użyć. Prawdopodobnie możesz sobie wyobrazić sytuację, w której istnieje pewna korzyść z uruchomienia wielu głodnych pamięci wątków na tym samym węźle NUMA dla komunikacji między niskimi opóźnieniami, ale gdy używają zdalnej pamięci do wysokiej przepustowości, a nie wrażliwych na opóźnienia rzeczy. Ale jest to dość niejasne; Zwykle zamiast celowo używając pamięci zdalnej, gdy można było użyć lokalnej, wystarczy podzielić wątki między węzłami NUMA i kazać im używać pamięci lokalnej.


(zwykle) nie używaj oprogramowania prefetch

Jedną z głównych zmian jest to, że hardware prefetch jest znacznie lepszy niż na P4 i może rozpoznawać wzorce dostępu do dość dużego kroku i wiele strumieni jednocześnie (np. jeden do przodu / do tyłu na stronę 4k). Instrukcja optymalizacji Intela opisuje niektóre szczegóły prefetcherów sprzętowych na różnych poziomach pamięci podręcznej dla ich mikroarchitektury z rodziny Sandybridge. Ivybridge i nowsze mają prefetch sprzętowy następnej strony, zamiast czekać na brak pamięci podręcznej na nowej stronie, aby uruchomić szybki start. (Zakładam, że AMD ma podobne rzeczy w swojej instrukcji optymalizacji.) Uważaj, że instrukcja Intela jest również pełna starych porad, z których część jest dobra tylko dla P4. Sekcje Sandybridge-specific są oczywiście dokładne dla SnB, ale np. un-laminowanie mikro-fused UOPs zmienione w HSW i instrukcja o tym nie wspomina .

W dzisiejszych czasach zwykle radzi się usunąć wszystkie SW prefetch ze starego kodu , i rozważyć tylko umieszczenie go z powrotem, jeśli profilowanie pokazuje brak pamięci podręcznej(i nie wysycasz przepustowości PAMIĘCI). Prefetching obu stron następny krok wyszukiwania binarnego może nadal pomóc. na przykład, gdy zdecydujesz, który element ma być następny, prefetch elementy 1/4 i 3/4, aby mogły załaduj równolegle z ładowaniem / sprawdzaniem środka.

Sugestia użycia oddzielnego wątku prefetch (6.3.4) jest całkowicie przestarzała, jak sądzę, i była dobra tylko na Pentium 4. P4 miał hyperthreading (dwa rdzenie logiczne współdzielące jeden rdzeń fizyczny), ale nie było wystarczająco dużo zasobów wykonania poza zamówieniem lub trace-cache, aby uzyskać przepustowość uruchamiającą dwa pełne wątki obliczeniowe na tym samym rdzeniu. Ale nowoczesne procesory (Sandybridge-family i Ryzen) są znacznie bardziej wydajne i powinny albo Uruchom prawdziwy wątek lub nie używaj hyperthreading (pozostaw drugi rdzeń logiczny bezczynny, aby wątek solo miał pełne zasoby.)

Oprogramowanie prefetch zawsze było "kruche" : odpowiednie magiczne numery strojenia, aby uzyskać przyspieszenie, zależą od szczegółów sprzętu, a może obciążenia systemu. Za wcześnie i jest eksmitowany przed obciążeniem popytu. Za późno i to nie pomaga. ten artykuł na blogu pokazuje kod + wykresy dla ciekawego eksperymentu w użyciu Prefetch SW na Haswell do wstępnego ustawiania niesekwencyjnej części problemu. Zobacz także jak prawidłowo używać instrukcji prefetch?. NT prefetch jest ciekawy, ale jeszcze bardziej kruchy (bo wczesna eksmisja z L1 oznacza, że trzeba przejść aż do L3 lub DRAM, a nie tylko L2). Jeśli potrzebujesz każdej ostatniej kropli wydajności, i możesz dostroić się do konkretnej maszyny, SW prefetch jest wart przeglądania dla dostępu sekwencyjnego, ale jeśli może {56]} nadal być spowolnieniem, jeśli masz wystarczająco dużo pracy ALU zbliżając się do wąskiego gardła w pamięci.


Rozmiar linii pamięci podręcznej nadal wynosi 64 bajty. (Przepustowość odczytu/zapisu L1D jest bardzo wysoka, a nowoczesne procesory mogą wykonać 2 obciążenia wektorowe na zegar + 1 magazyn wektorowy, jeśli wszystko trafi w L1D. Zobacz jak może być tak szybka pamięć podręczna?.) Z AVX512, rozmiar linii = szerokość wektora, więc można załadować / zapisać całą linię pamięci podręcznej w jednej instrukcji. (A więc każdy niewłaściwie dopasowany load / store przekracza granicę cache-line, zamiast co drugi dla 256b AVX1 / AVX2, który często nie spowalnia zapętlania tablicy, która nie była w L1D.)

Unaligned Load instructions have zero penalty if the address is aligned at runtime, but Kompilatory (szczególnie gcc) make better code when autovectorising if they know about any alignment guarantees. W rzeczywistości unaligned ops są na ogół szybkie, ale page-splits nadal boli (znacznie mniej na Skylake, choć; tylko ~ 11 dodatkowe cykle opóźnienia vs. 100, ale nadal przepustowość kary).


Jako Ulrich przewidywany, każdy multi-socket system jest obecnie NUMA: zintegrowane kontrolery pamięci są standardowe, tzn. nie ma zewnętrznego Northbridge. Ale SMP nie oznacza już wielu gniazd, ponieważ wielordzeniowe procesory są powszechne. (Procesory Intela od Nehalem do Skylake używały dużej pamięci podręcznej inclusive L3 jako zabezpieczenia dla spójności między rdzeniami.) Procesory AMD są inne, ale nie jestem tak jasny w szczegółach.

Skylake-X (AVX512) nie ma już wbudowanego L3, ale myślę, że nadal istnieje katalog tagów, który pozwala sprawdzić, co jest buforowane w dowolnym miejscu na chipie (a jeśli tak, gdzie) bez rzeczywistego nadawania snoopów do wszystkich rdzeni. SKX używa siatki zamiast szyny pierścieniowej , z generalnie gorszym opóźnieniem niż poprzednie wielordzeniowe Xeony, niestety.

Zasadniczo wszystkie porady dotyczące optymalizacji umieszczenia pamięci nadal obowiązują, tylko szczegóły dokładnie tego, co się dzieje, gdy nie można uniknąć błędów pamięci podręcznej lub sporów / align = "left" /


6.4.2 Atomic ops : benchmark pokazujący pętlę CAS-retry jako 4X gorszą niż sprzętowo arbitrated lock add prawdopodobnie nadal odzwierciedla maksymalny spór sprawa. Ale w prawdziwych programach wielowątkowych synchronizacja jest ograniczona do minimum (ponieważ jest kosztowna), więc argumentacja jest niska, a pętla CAS-retry zwykle udaje się bez konieczności ponownej próby.

C++11 std::atomic fetch_add skompiluje się do lock add (lub lock xadd jeśli używana jest wartość zwracana), ale algorytm wykorzystujący CAS do zrobienia czegoś, czego nie można zrobić za pomocą lockinstrukcji ed zwykle nie jest katastrofą. Use C++11 std::atomic lub C11 stdatomic zamiast GCC __sync wbudowane lub nowsze __atomic wbudowane, chyba że chcesz mieszać atomowy i niematomiczny dostęp do tego samego miejsca...

8.1 DCAS (cmpxchg16b): możesz nakłonić gcc do emitowania go, ale jeśli chcesz efektywnie ładować tylko połowę obiektu, potrzebujesz brzydkiego union hacki: jak zaimplementować licznik ABA w c++11 CAS?

8.2.4 pamięć transakcyjna: po kilku fałszywych startach (wypuszczonych następnie wyłączonych przez aktualizację mikrokodu z powodu rzadko wywoływanego błędu), Intel ma działającą pamięć transakcyjną w późnym modelu Broadwella i wszystkich procesorach Skylake. Projekt jest nadal , co David Kanter opisał dla Haswella. Jest sposób na przyśpieszenie kodu, który używa (i może wrócić do) zwykłego lock (zwłaszcza z pojedynczą blokadą dla wszystkich elementów kontenera, aby wiele wątków w tej samej sekcji krytycznej często nie kolidowało), lub do pisania kodu, który zna transakcje bezpośrednio.


7.5 Hugepages : anonymous transparent hugepages działa dobrze na Linuksie bez konieczności ręcznego używania hugetlbfs. Dokonaj alokacji >= 2mib z wyrównaniem 2MiB (np. posix_memalign, lub aligned_alloc to nie wymusza głupiego wymogu ISO C++17, aby zawieść, gdy size % alignment != 0).

Przypisanie anonimowe 2mib będzie domyślnie używać hugepages. Niektóre obciążenia (np. które używają dużych alokacji przez jakiś czas po ich utworzeniu) mogą skorzystać z
echo always >/sys/kernel/mm/transparent_hugepage/defrag aby zmusić jądro do defragmentacji fizycznej pamięci w razie potrzeby, zamiast wracać do stron 4k. (Zobacz dokumenty jądra ). Alternatywnie, użyj madvise(MADV_HUGEPAGE) po dokonaniu dużych przydziałów (najlepiej nadal z wyrównaniem 2MiB).


Dodatek B: Oprofile: Linux perf w większości wyparł oprofile. W przypadku szczegółowych zdarzeń specyficznych dla niektórych mikroarchitektur, użyj wrappera ocperf.py . np.

ocperf.py stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,\
branches,branch-misses,instructions,uops_issued.any,\
uops_executed.thread,idq_uops_not_delivered.core -r2 ./a.out

Aby uzyskać kilka przykładów użycia, zobacz czy MOV x86 może być naprawdę "wolny"? Dlaczego nie mogę tego odtworzyć?.

 41
Author: Peter Cordes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-25 15:15:30