Od pewnego czasu dużo szukałem i czytałem o wyrównaniu pamięci, o tym, jak to działa i jak z niego korzystać. Najbardziej odpowiedni artykuł, który znalazłem na razie, to ten .
Ale mimo to wciąż mam kilka pytań:
- Z wbudowanego systemu często mamy ogromną ilość pamięci w naszym komputerze, co sprawia, że zarządzanie pamięcią jest o wiele mniej krytyczne, jestem całkowicie zoptymalizowany, ale teraz to naprawdę coś, co może zrobić różnicę, jeśli porównamy ten sam program z lub bez uporządkowania i wyrównania pamięci?
- Czy wyrównanie pamięci ma inne zalety? Czytałem gdzieś, że procesor działa lepiej / szybciej z wyrównaną pamięcią, ponieważ zajmuje to mniej instrukcji do przetworzenia (jeśli jeden z was ma link do artykułu / testu?), Czy w takim przypadku różnica jest naprawdę znacząca? Czy jest więcej zalet niż te dwa?
- W linku do artykułu w rozdziale 5 autor mówi:
Uwaga: w C ++ klasy wyglądające jak struktury mogą złamać tę regułę! (Czy to robią, czy nie, zależy od sposobu implementacji klas podstawowych i wirtualnych funkcji składowych i zależy od kompilatora.)
Artykuł mówi głównie o strukturach, ale czy na tę deklarację wpływa również deklaracja zmiennych lokalnych?
Czy masz pojęcie o tym, jak wyrównanie pamięci działa dokładnie w C ++, ponieważ wydaje się, że ma pewne różnice?
To poprzednie pytanie zawiera słowo „wyrównanie”, ale nie zawiera żadnych odpowiedzi na powyższe pytania.
c++
c
optimization
memory-usage
speed
Kane
źródło
źródło
Odpowiedzi:
Tak, zarówno wyrównanie, jak i uporządkowanie danych może mieć dużą różnicę w wydajności, nie tylko o kilka procent, ale od kilku do wielu setek procent.
Weź tę pętlę, dwie instrukcje mają znaczenie, jeśli uruchomisz wystarczającą liczbę pętli.
Z pamięcią podręczną i bez niej oraz z wyrównywaniem z rzutem pamięci podręcznej i bez niego w przewidywaniu gałęzi i można znacznie różnić wydajność tych dwóch instrukcji (tyknięcia zegara):
Test wydajności, który możesz bardzo łatwo zrobić sam. dodaj lub usuń kropki wokół testowanego kodu i wykonaj dokładną synchronizację, przenieś testowane instrukcje wzdłuż odpowiednio szerokiego zakresu adresów, aby dotknąć krawędzi linii pamięci podręcznej itp.
To samo dotyczy dostępu do danych. Niektóre architektury narzekają na niezrównany dostęp (na przykład wykonanie 32-bitowego odczytu pod adresem 0x1001), powodując błąd danych. Niektóre z nich można wyłączyć i przejąć wydajność. Inne, które umożliwiają dostęp bez wyrównania, po prostu dostają wydajność.
Czasami są to „instrukcje”, ale przez większość czasu są to cykle zegara / autobusu.
Spójrz na implementacje memcpy w gcc dla różnych celów. Powiedzmy, że kopiujesz strukturę 0x43 bajtów, możesz znaleźć implementację, która kopiuje jeden bajt, pozostawiając 0x42, a następnie kopiuje 0x40 bajtów w dużych wydajnych porcjach, a ostatnia 0x2 może to zrobić jako dwa pojedyncze bajty lub jako transfer 16-bitowy. Wyrównanie i cel wchodzą w grę, jeśli adresy źródłowy i docelowy są na tym samym wyrównaniu, powiedzmy 0x1003 i 0x2003, wtedy możesz zrobić jeden bajt, następnie 0x40 w dużych porcjach, a następnie 0x2, ale jeśli jeden to 0x1002, a drugi 0x1003, to dostaje naprawdę brzydkie i naprawdę wolne.
Przez większość czasu są to cykle autobusowe. Lub gorsza liczba przelewów. Weź procesor z 64-bitową magistralą danych, taką jak ARM, i wykonaj transfer czterech słów (odczyt lub zapis, LDM lub STM) pod adresem 0x1004, to jest adres wyrównany do słów i całkowicie legalny, ale jeśli magistrala ma 64 szerokość bitów jest prawdopodobne, że pojedyncza instrukcja zamieni się w trzy transfery w tym przypadku 32-bitowy przy 0x1004, 64-bitowy przy 0x1008 i 32-bitowy przy 0x100A. Ale gdybyś miał tę samą instrukcję, ale pod adresem 0x1008, mógłby wykonać pojedynczy transfer czterech słów pod adresem 0x1008. Każdy transfer ma przypisany czas konfiguracji. Tak więc różnica adresów od 0x1004 do 0x1008 może być kilka razy szybsza, nawet / esp podczas korzystania z pamięci podręcznej i wszystkie są trafieniami do pamięci podręcznej.
Mówiąc o tym, nawet jeśli wykonasz dwa słowa odczytane pod adresem 0x1000 vs 0x0FFC, 0x0FFC z brakami pamięci podręcznej spowoduje odczyt dwóch linii pamięci podręcznej, gdzie 0x1000 to jedna linia pamięci podręcznej, i tak zostaniesz ukarany za odczytywanie linii losowej losowo dostęp (odczyt większej ilości danych niż używanie), ale to podwaja się. Sposób, w jaki struktury są wyrównane lub dane w ogóle, a także częstotliwość uzyskiwania dostępu do tych danych itp., Mogą powodować przeładowanie pamięci podręcznej.
Możesz skończyć z rozbijaniem danych, tak że podczas przetwarzania danych możesz tworzyć eksmisje, możesz mieć naprawdę pecha i skończyć z wykorzystaniem tylko niewielkiej części pamięci podręcznej, a podczas przeskakiwania przez nią następna kropla danych zderza się z poprzednią kroplą . Przez zmieszanie danych lub ponowne uporządkowanie funkcji w kodzie źródłowym itp. Możesz tworzyć lub usuwać kolizje, ponieważ nie wszystkie pamięci podręczne są równe, kompilator nie pomoże ci tutaj. Nawet wykrywanie spadku wydajności lub poprawy zależy od Ciebie.
Wszystko, co dodaliśmy, aby poprawić wydajność, szersze magistrale danych, potoki, pamięci podręczne, przewidywanie rozgałęzień, wiele jednostek / ścieżek wykonawczych itp. Najczęściej pomogą, ale wszystkie mają słabe punkty, które można wykorzystać celowo lub przypadkowo. Kompilator lub biblioteki niewiele mogą na to poradzić, jeśli interesuje Cię wydajność, musisz dostroić, a jednym z największych czynników dostrajających jest wyrównanie kodu i danych, a nie tylko 32, 64, 128, 256 granice bitów, ale także tam, gdzie rzeczy są względem siebie nawzajem, mocno używane pętle lub ponownie wykorzystywane dane nie powinny lądować w ten sam sposób pamięci podręcznej, każda z nich chce mieć własną. Kompilatory mogą pomóc np. W zamawianiu instrukcji dla architektury super skalarnej, ponownym rozmieszczaniu instrukcji, które nie mają znaczenia,
Największym niedopatrzeniem jest założenie, że procesor jest wąskim gardłem. Nie było to prawdą przez dekadę lub dłużej, karmienie procesora jest problemem i tam właśnie pojawiają się problemy, takie jak uderzenia wydajności wyrównania, przerzucanie pamięci podręcznej itp. Przy odrobinie pracy, nawet na poziomie kodu źródłowego, ponowne uporządkowanie danych w strukturze, porządkowanie deklaracji zmiennych / struktur, porządkowanie funkcji w kodzie źródłowym i trochę dodatkowego kodu do wyrównywania danych, może kilkukrotnie poprawić wydajność więcej.
źródło
Tak, wyrównanie pamięci nadal ma znaczenie.
Niektóre procesory faktycznie nie mogą wykonywać odczytów z adresów nieprzystosowanych. Jeśli pracujesz na takim sprzęcie i przechowujesz liczby całkowite niewyrównane, prawdopodobnie będziesz musiał je przeczytać z dwiema instrukcjami, a następnie kilkoma instrukcjami, aby wprowadzić różne bajty we właściwe miejsca, abyś mógł z nich faktycznie skorzystać . Tak dostosowane dane mają krytyczne znaczenie dla wydajności.
Dobra wiadomość jest taka, że tak naprawdę nie musisz się tym przejmować. Prawie każdy kompilator dla prawie dowolnego języka będzie wytwarzał kod maszynowy, który będzie spełniał wymagania dotyczące wyrównania systemu docelowego. Musisz o tym pomyśleć tylko wtedy, gdy przejmujesz bezpośrednią kontrolę nad reprezentacją danych w pamięci, co nie jest konieczne tak często, jak kiedyś. To interesująca rzecz, o której warto wiedzieć, i absolutnie niezbędna, aby wiedzieć, czy chcesz zrozumieć wykorzystanie pamięci z różnych tworzonych struktur i jak może reorganizować rzeczy, aby były bardziej wydajne (unikając wypełniania). Ale chyba, że potrzebujesz takiej kontroli (a dla większości systemów, których po prostu nie potrzebujesz), możesz z radością przejść całą karierę, nie wiedząc o tym ani nie dbając o nią.
źródło
Tak, nadal ma to znaczenie, aw niektórych algorytmach krytycznych dla wydajności nie można polegać na kompilatorze.
Wymienię tylko kilka przykładów:
Jeśli nie pracujesz nad algorytmami krytycznymi dla wydajności, po prostu zapomnij o wyrównaniu pamięci. Nie jest tak naprawdę potrzebny do normalnego programowania.
źródło
Staramy się unikać sytuacji, w których ma to znaczenie. Jeśli to ma znaczenie, to ma znaczenie. Niedopasowane dane zdarzały się na przykład podczas przetwarzania danych binarnych, co wydaje się być obecnie unikane (ludzie często używają XML lub JSON).
JEŚLI w jakiś sposób uda ci się utworzyć nieprzypisaną tablicę liczb całkowitych, to na typowym procesorze Intel proces przetwarzania tej tablicy będzie działał nieco wolniej niż w przypadku wyrównanych danych. Na procesorze ARM działa nieco wolniej, jeśli powiesz kompilatorowi, że dane nie są wyrównane. Może albo działać okropnie, dużo wolniej, albo dawać błędne wyniki, w zależności od modelu procesora i systemu operacyjnego, jeśli użyjesz niepasowanych danych bez informowania kompilatora.
Wyjaśnienie odwołania do C ++: W C wszystkie pola w strukturze muszą być przechowywane w porządku rosnącym. Więc jeśli masz pola char / double / char i chcesz wszystko wyrównać, będziesz miał jeden bajt char, siedem bajtów nieużywanych, osiem bajtów double, jeden bajt char, siedem bajtów nieużywanych. W strukturach C ++ jest to samo dla kompatybilności. Ale w przypadku struktur kompilator może zmieniać kolejność pól, więc możesz mieć jeden bajt char, inny bajt char, sześć bajtów nieużywanych, 8 bajtów podwójnie. Używanie 16 zamiast 24 bajtów. W strukturach C programiści zwykle unikają takiej sytuacji i przede wszystkim umieszczają pola w innej kolejności.
źródło
Wiele dobrych punktów wspomniano już w powyższych odpowiedziach. Wystarczy dodać nawet w systemach niewbudowanych, które zajmują się wyszukiwaniem / wyszukiwaniem danych, wydajność pamięci i czasy dostępu są tak ważne, że napisano inaczej niż kod asemblera.
Polecam również warto przeczytać: http://dewaele.org/~robbe/thesis/writing/references/what-every-programmer-should-know-about-memory.2007.pdf
źródło
Tak. Nie. To zależy.
Twoja aplikacja będzie miała mniejszą pamięć i będzie działać szybciej, jeśli zostanie odpowiednio wyrównana. W typowej aplikacji komputerowej nie będzie to miało znaczenia poza rzadkimi / nietypowymi przypadkami (np. Aplikacja zawsze kończy się tym samym wąskim gardłem wydajności i wymaga optymalizacji). Oznacza to, że aplikacja będzie mniejsza i szybsza, jeśli zostanie odpowiednio wyrównana, ale w większości praktycznych przypadków nie powinna wpływać na użytkownika w taki czy inny sposób.
To może być. Podczas pisania kodu należy o tym pamiętać (być może), ale w większości przypadków po prostu nie powinno to mieć znaczenia (to znaczy, wciąż zmieniam zmienne składowe według wielkości pamięci i częstotliwości dostępu - co powinno ułatwić buforowanie - ale robię to dla łatwość użycia / odczytu i refaktoryzacji kodu, nie do celów buforowania).
Czytałem o tym, kiedy wyszedł alignof rzeczy (C ++ 11?), Nie przejmowałem się tym od tego czasu (obecnie zajmuję się głównie aplikacjami komputerowymi i tworzeniem serwerów backendowych).
źródło