2012-03-01 15 views
16

V8 wymaga deklaracji HandleScope w celu wyczyszczenia dowolnych lokalnych uchwytów utworzonych w zasięgu. Rozumiem, że HandleScope usunie te uchwyty do zbierania śmieci, ale interesuje mnie, dlaczego każda klasa lokalna nie wykonuje samych dereferencji jak większość wewnętrznych pomocników typu ref_ptr.Jakie są założenia projektowe dotyczące HandleScope?

Uważam, że HandleScope może to zrobić bardziej efektywnie, przesyłając dużą liczbę uchwytów naraz, a nie pojedynczo, jak w klasie o typie ref_ptr.

Odpowiedz

3

Nota prawna: To nie może być oficjalna odpowiedź, bardziej z koniunkcji z mojej strony; ale dokumentacja v8 nie jest użyteczna na ten temat. Więc może mi udowodnić, że się mylę.

Z mojego punktu widzenia przy opracowywaniu różnych aplikacji opartych na V8. Jest to środek radzący sobie z różnicą między środowiskiem C++ i javaScript.

Wyobraź sobie następującą sekwencję, którą wskaźnik do samodzielnego dereferencji może złamać system.

  1. JavaScript nazywa się C++ owinięty funkcja v8: powiedzmy helloworld()
  2. funkcja
  3. C++ tworzy V8 :: uchwyt wartości "hello world = x"
  4. C++ zwraca wartość V8 maszyna wirtualna
  5. funkcja
  6. C++ robi jej zwykle czyszczenia zasobów, w tym dereferencing uchwytów
  7. Innym C++ funkcja/procesu, nadpisuje uwolniony obszar pamięci
  8. V8 czyta uchwyt: a dane nie jest już taka sama

A to tylko powierzchnia skomplikowanej niezgodności pomiędzy dwóch; „piekło @ (# ...!” Stąd w celu rozwiązania różnych problemów z podłączeniem JavaScript VM (Virtual Machine) do C++ sprzęgania kodu, uważam, że zespół programistów, postanowił uprościć kwestię poprzez następujące ...

  • Wszystkie zmienne uchwyty, mają być przechowywane w "wiadra" aka HandleScopes, być zbudowany/skompilowany/run/zniszczony przez ich odpowiedni C++ kod, gdy potrzebne.
  • Dodatkowo wszystkim funkcja obsługuje, mają odnoszą się tylko do C++ statycznych funkcji (wiem, to jest irytujące), co zapewnia „istnienie” wywołania funkcji niezależnie od konstruktorów/destructor.

myśleć o tym z punktu widzenia rozwoju, w którym wyznacza bardzo silne rozróżnienie między deweloperami JavaScript VM i C++ integracja zespołu (team Chrome dev?). Umożliwienie obu stronom pracy bez wzajemnego zakłócania się.

Wreszcie może to być również uproszczenie, aby emulować wiele maszyn wirtualnych: ponieważ v8 było pierwotnie przeznaczone dla google chrome. W związku z tym proste tworzenie i niszczenie HandleScope za każdym razem, gdy otwieramy/zamykamy kartę, znacznie ułatwia zarządzanie GC, szczególnie w przypadku, gdy masz wiele maszyn wirtualnych (każda zakładka w chrome).

+0

Czy każda karta w chrome nie działa w oddzielnym procesie? – Pete

+0

@Pete HandleScope może być (opcjonalnie) uruchomiony w swoich własnych wątkach (również w procesie). Dodatkowo, ponieważ każda z nich jest ich własną zmienną przestrzenią, nie ma potrzeby zapewnienia, że ​​ich zmienne są "bezpieczne dla wątków" między sobą. – PicoCreator

5

Oto jak rozumiem kod źródłowy the documentation i . Ja też może być całkowicie nie tak, ponieważ nie jestem programistą V8, a dokumentacja jest skąpa.

Śmieciarz będzie niekiedy przenosił rzeczy z jednego miejsca pamięci do drugiego, a podczas jednego takiego przeciągnięcia sprawdza również, które obiekty są nadal dostępne, a które nie. W przeciwieństwie do typów zliczających odniesienia, takich jak std::shared_ptr, jest w stanie wykryć i zebrać cykliczne struktury danych. Aby wszystko to działało, V8 musi dobrze wiedzieć, jakie obiekty są osiągalne.

Z drugiej strony obiekty są tworzone i usuwane całkiem sporo podczas wewnętrznych obliczeń niektórych obliczeń. Nie potrzebujesz zbyt dużego narzutu dla każdej takiej operacji. Aby to osiągnąć, należy utworzyć stos uchwytów. Każdy obiekt wymieniony w tym stosie jest dostępny od jakiegoś uchwytu w niektórych obliczeniach C++. Oprócz tego istnieją trwałe porty, które prawdopodobnie wymagają więcej pracy do skonfigurowania i które mogą przetrwać poza obliczeniami C++.

Posiadanie stosu odnośników wymaga użycia tego w sposób podobny do stosu. W tym stosie nie ma "nieważnego" znaku. Wszystkie obiekty od dołu do góry stosu są poprawnymi odniesieniami do obiektów. Sposób zapewnienia tego jest LocalScope. Utrzymuje rzeczy w hierarchii. W odniesieniu liczone wskaźniki można zrobić coś takiego:

shared_ptr<Object>* f() { 
    shared_ptr<Object> a(new Object(1)); 
    shared_ptr<Object>* b = new shared_ptr<Object>(new Object(2)); 
    return b; 
} 
void g() { 
    shared_ptr<Object> c = *f(); 
} 

Tutaj przedmiot 1 jest tworzony pierwszy następnie przedmiot 2 jest tworzony, a następnie powraca funkcyjne i przedmiot 1 jest zniszczony, to obiekt 2 jest zniszczona . Kluczową kwestią jest to, że istnieje moment, w którym obiekt 1 jest nieważny, ale obiekt 2 jest nadal ważny. To właśnie chce uniknąć LocalScope.

Niektóre inne implementacje GC badają stos C i szukają znalezionych w nich wskaźników. Ma to dużą szansę na fałszywe alarmy, ponieważ rzeczy, które w rzeczywistości są danymi, mogą zostać błędnie zinterpretowane jako wskaźnik. Dla osiągnięcia tego może wydawać się nieszkodliwe, ale przy przepisywaniu wskaźników, ponieważ poruszasz obiektami, może to być śmiertelne. Ma wiele innych wad i wiele zależy od tego, jak działa niski poziom implementacji języka. V8 unika tego poprzez utrzymywanie stosu uchwytów oddzielnie od stosu wywoływania funkcji, przy jednoczesnym zapewnieniu, że są one wystarczająco wyrównane, aby zagwarantować wspomniane wymagania hierarchii.

Aby zaoferować jeszcze jedno porównanie: odniesienia do obiektów przez jeden shared_ptr stają się kolekcjonerskie (i tak naprawdę zostaną zebrane) po zakończeniu zakresu bloku C++. Obiekt oznaczony jako v8::Handle stanie się kolekcjonerski po opuszczeniu najbliższego otaczającego zakresu, który zawierał obiekt HandleScope. Tak więc programiści mają większą kontrolę nad ziarnistością operacji na stosie.W ciasnej pętli, w której wydajność jest ważna, może być użyteczne utrzymywanie pojedynczego HandleScope dla całego obliczenia, więc nie będziesz musiał tak często uzyskiwać dostępu do struktury danych stosu uchwytów. Z drugiej strony, spowoduje to zatrzymanie wszystkich obiektów przez cały czas obliczeń, co byłoby bardzo złe, gdyby to była pętla powtarzająca się w wielu wartościach, ponieważ wszystkie z nich byłyby przechowywane do końca. Ale programista ma pełną kontrolę i może zorganizować wszystko w najbardziej odpowiedni sposób.

Osobiście bym upewnić skonstruować HandleScope

  • Na początku każdej funkcji, które mogą być wywoływane z zewnątrz kodzie. Zapewni to, że twój kod oczyści się po nim.
  • W ciele każdej pętli, która może widzieć więcej niż trzy lub więcej iteracji, tak aby zachować tylko zmienne z bieżącej iteracji.
  • Wokół każdego bloku kodu, po którym następuje wywołanie wywołania zwrotnego, ponieważ gwarantuje to, że twoje rzeczy mogą zostać wyczyszczone, jeśli wywołanie zwrotne wymaga więcej pamięci.
  • Ilekroć wydaje mi się, że coś może wytwarzać znaczne ilości danych pośrednich, które powinny zostać oczyszczone (lub przynajmniej stać się kolekcjonerskimi) tak szybko, jak to możliwe.

W ogóle nie chciałbym tworzyć HandleScope dla każdej funkcji wewnętrznej, jeśli mogę mieć pewność, że każda inna funkcja nazywając to będzie już utworzyły HandleScope. Ale to prawdopodobnie kwestia gustu.

+0

To jest doskonała odpowiedź. Dziękuję za tę analizę. – joshperry

Powiązane problemy