2013-09-02 28 views
5

Mam zadanie, w którym wymagane jest zmierzenie czasu oczekiwania na dostęp do danych w pamięci podręcznej L1, L2 i L3, a także pamięci głównej. To powinno być zrobione w C.x86 Assembly Cache Store

Spędziłem kilka godzin na badaniu sposobów mierzenia opóźnienia pamięci podręcznej i okazało się bardzo mało. Pobrałem niektóre narzędzia do testowania porównawczego, które dały mi czasy dostępu do pamięci podręcznej, ale nigdzie się nie dostałem, jeśli chodzi o implementację tego w moim własnym kodzie. Rozumiem, że to, co dzieje się z pamięcią podręczną, nie należy do mnie w C.

Moja następna myśl była taka, że ​​gdybym mógł wymusić wypełnienie pamięci podręcznej czymś z zestawu x86 (pierwsza myśl), to po prostu wykonaj zegar(), dostęp(), zegar() na tych właśnie załadowanych danych, przypuszczalnie czas byłby dokładnym (ish) czasem dostępu, ponieważ wiem, że powinien on być znaleziony w pamięci podręcznej, ponieważ właśnie tam umieściłem go z moim wbudowanym modułem asm lub podobną metodą.

Jeśli ktokolwiek mógłby zaoferować wgląd w moje zadanie, byłoby to fantastyczne. Niezależnie od tego, czy mówię mi, że jestem szalony, bo chcę użyć Asma, by załadować coś w skrzynce lub przedstawić mi coś, co może mi pomóc.

Dziękuję bardzo!

+0

Może to podobne pytanie może być pomocne: http://stackoverflow.com/a/12697439/358328 –

+0

Podczas gdy to pytanie jest istotne, nie jest skierowane na pomiar opóźnienia, który jest jedyną rzeczą, zmartwiony o. Dzięki za post! Próbuję pracować od tego miejsca. – mrkanaly

Odpowiedz

7

Nie ma powodu, aby w ogóle używać do tego zadania. Twoje rozwiązanie nie wymaga montażu C również działa. Zakładam, że korzystasz z systemu operacyjnego, więc będzie to przeszkadzało w twoim pomiarze, zarówno przy wykonywaniu zadań, o których wiesz, że wiesz, gdzie są, jak i przy mierzeniu tego, co myślisz, że mierzysz.

Podstawy pamięci podręcznej dotyczące wykonywania tych pomiarów ... powiedzmy, że istnieją cztery warstwy pamięci. L1, najszybszy, ale także najdroższy i najmniejszy. Następnie L2. wolniej, nie tak drogie, prawdopodobnie większe niż L1. L3 tańszy, wolniejszy, większy, a potem pamięć główna najwolniejsza, najtańsza i największa.

Powiedzmy, że mamy cztery porcje pamięci, z którymi będziemy pracować z A, B, C i D. L1 może pomieścić tylko jedną porcję na raz. L2 dwa na raz, L3 trzy z czterech i pamięć główna wszystkie cztery.

Jeśli zrobimy odczyt, najpierw przechodzi przez L1, jeśli jest brak, to L2, jeśli brak, to L3, a jeśli brak, to zawsze będzie w pamięci głównej. Zrozum, że te dane są buforowane w drodze powrotnej, więc L3, L2 i L1 będą zawierały właśnie przeczytane dane, ewakuując je w razie potrzeby (nie zawsze prawdziwe, ale przyjmij ten prosty model pamięci podręcznej, aby zrozumieć, jak wykonać swoje zadanie). Jeśli więc przeczytamy fragment A, wówczas L1, L2 i L3 będą zawierały fragment A. Teraz w tym hipotetycznym modelu, jeśli odczytamy fragment B, wówczas L1 będzie zawierał B, eksykcję A. L2 będzie zawierać A i b, a l3 będzie zawierać A i B. Czytaj C i L1 będą zawierały C, eksmitując B, powiedzmy, że L2 wybiera eksmitować A, i zawiera B i C, a L3 zawiera A, B i C. Czytaj D i L1 będą zawierały C, powiedzmy L2 eksmisje B i zawiera C i D, i powiedzmy, że L3 eksmituje A i zawiera B, C i D.

Załóżmy, że nie wiemy dokładnie, jak każda pamięć podręczna wybiera, co należy eksmitować i co zachować. Załóżmy jednak, że wiemy lub możemy dowiedzieć się ze specyfikacji płyty głównej lub z innych źródeł, jak duża jest każda pamięć podręczna. Jeśli powyższy przykład wystąpił w tej kolejności, a L1 ma D, L2 ma C i D, L3 ma B, C i D, a główne ma wszystkie cztery a, b, c, d. Następnie, jeśli w tym stanie odczytamy cały blok A i czas, w jakim jesteśmy w teorii, odczytując go z pamięci głównej, to nie tylko czas na odczytanie tej pamięci, ale także, jeśli któraś z eksmitowanych pamięci się zmieniła, musi napisz do góry możliwe trafienia do końca. Ale najlepiej, jeśli robisz głównie czytasz, będziesz mieć czas głównie na czytanie.

Powiedzmy, że znaleźliśmy się w sytuacji, w której fragment D jest w l1, cid w l2, b, c, d w l3 i czytamy cały fragment B i czas. Czy to nie byłby czas na dostęp do L3? z tymi samymi warunkami początkowymi odczytanie C dałoby nam czas l2. Przy tych samych warunkach początkowych odczytanie D byłoby właściwym czasem w czasie l1?

Sztuką jest dostać się do tych warunków. Rozmiary pamięci podręcznych najprawdopodobniej nie są takie, że l2 jest dwa razy większe od l1 i tak aby całkowicie kontrolować, co jest w L1, musisz odczytać wystarczającą ilość danych, aby wypełnić L1. Moreso, jeśli odczytywałeś dane wielkości L3, wtedy teoretycznie L3 ma wszystkie te dane, L2 ma ostatnią L2 ilość tych danych, a L1 ma ostatnią LI ilość tych danych.

Korzystanie z pamięci podręcznej danych jest łatwiejsze niż pamięć podręczna instrukcji, ale możesz to zrobić w dowolny sposób, potrzebujesz co najmniej L3 wielkości instrukcji w pamięci głównej, dużej ilości nops. wykonanie liniowego fragmentu instrukcji nie różni się od czytania liniowej części pamięci. o ile chodzi o cykle odczytu. Instrukcja jest łatwiejsza w zakresie włączania i korzystania z pamięci podręcznej I. Aby włączyć buforowanie danych może być lub nie być proste w zależności od systemu operacyjnego i sposobu zarządzania pamięcią.

+0

Dziękuję bardzo za to. To z pewnością świetna odpowiedź. Korzystając z tego rodzaju myślenia, udało mi się to osiągnąć. – mrkanaly

1

Powinieneś być w stanie uniknąć asemblera patrząc na dane wyjściowe asemblera kompilatora, aby zrozumieć rzeczywiste operacje.

Nawet jeśli otrzymasz zegar o wysokiej rozdzielczości, niewiele możesz zrobić, aby uzyskać zniżkę dla systemu operacyjnego podczas uruchamiania testu porównawczego. Będziesz musiał wykonać wiele prób, aby uzyskać znaczące wyniki.

Zamiast próbować umieszczać instrukcje w pamięci podręcznej, może być łatwiej zezwolić procesorowi na ładowanie ich w trakcie ich działania. Jeśli umieścisz różne ilości wypełniacza w procedurach, możesz uzyskać wyrównanie linii pamięci podręcznej do tego, co chcesz.

+0

Dziękuję za odpowiedź. Jednak jestem bardzo zdezorientowany tym, co tu mówisz. Ostatecznie próbuję zmierzyć opóźnienie każdego poziomu pamięci podręcznej. Nie jestem zaznajomiony z wyrównaniem pamięci podręcznej i nie jestem pewien, czy istotność przeglądania danych wyjściowych asemblera kompilatora jest większa niż widzenie całego zestawu naraz. – mrkanaly

+0

Należy przejrzeć dokumentację procesora i jego opcji pamięci podręcznej. Następnie spójrz na lokalizacje kodu w pamięci za pomocą debuggera, aby zobaczyć, w jaki sposób są one dostosowane do linii pamięci podręcznej. Następnie możesz policzyć liczbę razy, gdy procesor uzyskuje dostęp do każdej linii, aby sprawdzić, ile trafień w pamięci podręcznej masz. W zależności od procesora i narzędzi, możesz uzyskać aktualne liczniki, ale ta sugestia zadziała bez dodatkowej obsługi procesora. – Pekka

+0

Wielkość buforów można określić na podstawie dokumentacji architektury procesora. Wyrównanie będzie wtedy zależało od tego, jak twój OS mapuje wirtualną przestrzeń adresową na pamięć fizyczną. Alternatywnie może istnieć interfejs API umożliwiający dostęp do tych informacji. W systemie Windows można użyć klasy [WMI Win32_Processor] (http://msdn.microsoft.com/en-us/library/aa394373 (v = vs.85) .aspx) w celu określenia tych wartości. Celem zobaczenia asemblera jest umożliwienie ci wypracowania adresów poszczególnych elementów kodu po ich załadowaniu po uruchomieniu testu porównawczego. – Pekka

1

Naprawdę nie musisz patrzeć na to na podstawie szczegółowości linii, jest dość skomplikowany w utrzymaniu (jak pokazuje dwelch w swojej bardzo dobrej odpowiedzi) i prawie niemożliwy do zmierzenia, chyba że powtórzysz wystarczająco dużo czasu (co z kolei może skomplikować utrzymywanie odpowiednich warunków wymuszania trafienia na określony poziom pamięci podręcznej).

Zamiast tego możesz zacząć od napisania prostej tablicy, która znajduje się w sąsiadującej przestrzeni fizycznej (możesz potrzebować pewnych ulepszeń, jeśli system operacyjny ma rozbudowany mechanizm przypisywania stron). Wypełnij dane do tej tablicy (wystarczy jeden dostęp na cacheline), a następnie rozpocznij czytanie go wielokrotnie. Jeśli rozmiar tablicy jest wystarczająco mały, aby zmieścić się w L1 (dla 32k np. Można przydzielić 32 znaki lub nieco mniej i uzyskać dostęp do wszystkich 64 elementów), po wystarczającej liczbie powtórzeń, powinieneś uzyskać dostęp do większości dostępów. Możesz mieć przypadki narożne z zakłóceniami z innymi buforowanymi liniami, takie jak wpisy do map, stos lub inne zmienne sterty, ale w większości przypadków otrzymujesz trafienie L1, więc ładnie się wyrównuje. Nawet zdarzenia takie jak zmiana kontekstu (na wypadek, gdyby nie można było kontrolować systemu, aby temu zapobiec) znikną, jeśli powtórzysz je wystarczająco długo, aby uzyskać stabilne wyniki.

Następnie zacznij stopniowo zwiększać rozmiar zestawu danych. Po przejściu przez L1, powinieneś zobaczyć wyraźną degradację czasu dostępu (całkowity czas podzielony przez # dostępy). Zauważ, że sposób, w jaki pamięć podręczna działa z LRU, że masz dostęp do swojej tablicy w liniowej kolejności oznacza, że ​​w chwili, gdy jest wystarczająco duży, aby nie pasował do L1, nie powinieneś otrzymać częściowej korzyści, ponieważ ostatnie elementy prawdopodobnie wyprowadzą pierwszy w samą porę, aby uniemożliwić następną iterację ich znalezienia (chociaż nadal można cieszyć się z tego, że obciążenia mogą nie działać w nowoczesnych procesorach). Idąc dalej, gdy osiągniesz rozmiar L2 mniej więcej (jeśli L2 w twoim systemie nie jest ściśle zawarty, możesz mieć niewielką korzyść z posiadania L1 + L2, ale opisany wzór dostępu powinien temu zapobiec prawie całkowicie). Potem znowu, gdy uderzysz w rozmiar L3.

Należy pamiętać, że niektóre funkcje sprzętowe mogą komplikować sprawy - przede wszystkim jest to program pobierania wstępnego HW, który jest prawie gotowy do kopania i pobierania linii przed tobą. Jeśli to możliwe, powinieneś wyłączyć to poprzez BIOS, w przeciwnym razie możesz przeskoczyć w większych krokach (128 lub nawet 256 zamiast 64) - pamięci podręczne są najprawdopodobniej bezpośrednio odwzorowane (z pewną liczbą skojarzeń), więc to spowodowałoby stres tylko w jednym 2-4 zestawy i pozostawiając resztę pustą, co jest w porządku (o ile pamiętasz, aby podzielić czas przez nową liczbę dostępów). Przełamałoby to również strumień wystarczająco, aby uzyskać rzeczywisty czas, a nie wspomagany wstępem (możesz również mieć prefetcher oparty na kroku, ale zazwyczaj nie jest on tak silny jak streamer).