2011-06-21 15 views
22

Piszemy kod bajtowy dla języka skompilowanego na wysokim poziomie, a po odrobinie profilowania i optymalizacji stało się jasne, że obecne największe obciążenie związane z wydajnością to instrukcja switch, z której korzystamy. aby przejść do przypadków kodu bajtowego.Adres etykiet (MSVC)

Sprawdziliśmy, jak wyciągnąć adres etykiety każdej sprawy i zapisać ją w strumieniu samego kodu bajtowego, a nie ID instrukcji, który zwykle włączamy. Jeśli to zrobimy, możemy pominąć tabelę skoku i bezpośrednio przejść do lokalizacji kodu aktualnie wykonywanej instrukcji. Działa to fantastycznie w GCC, jednak MSVC nie obsługuje takiej funkcji.

Próbowaliśmy użyć wbudowanego zestawu, aby pobrać adres etykiet (i przeskoczyć do nich), ale działa on jednak, używając wbudowanego zestawu, dzięki czemu można uniknąć całej funkcji przez optymalizator MSVC.

Czy istnieje sposób, aby umożliwić optymalizatorowi nadal przebiegać nad kodem? Niestety, nie możemy wyodrębnić wbudowanego zestawu do innej funkcji niż ta, w której zostały wykonane etykiety, ponieważ nie ma możliwości odwołania się do etykiety dla innej funkcji, nawet w złożonym zestawie. Wszelkie myśli lub pomysły? Twój wkład jest bardzo doceniany, dzięki!

+3

Czy próbowałeś wskaźników funkcyjnych? –

+0

Co powiesz na umieszczanie adresów funkcji zamiast adresów etykiet w bajtodzie? Następnie masz jedną funkcję dla każdego identyfikatora instrukcji. Chyba że twoja pętla pobierania-wykonywania jest w twojej dużej funkcji-z-etykietami. –

+0

Jeśli użyłbym funkcji dla każdego z przypadków i użytych wskaźników funkcji zamiast adresów etykiet, to by działało.Jednak czuję, że obciążenie wywołane przez funkcję byłoby tak wielkie, że unieważniłoby jakąkolwiek wydajność, nawet jeśli funkcja była trywialna (brak argumentów, brak powrotu). Wypróbuję to i dzięki za publikację. – Trevor

Odpowiedz

15

Jedynym sposobem na osiągnięcie tego w MSVC jest za pomocą zestawu inline (która w zasadzie gnojki za x64):

int _tmain(int argc, _TCHAR* argv[]) 
{ 
case_1: 
    void* p; 
    __asm{ mov [p],offset case_1 } 
    printf("0x%p\n",p); 
    return 0; 
} 

Jeśli planujesz robić coś takiego, to najlepszym sposobem byłoby napisać cały interpreter w zespole następnie łączy go z głównym binarnym za pośrednictwem łącznika (to właśnie zrobił LuaJIT i jest to główny powód, dla którego VM jest tak oślepiająco szybka, kiedy nie działa z JIT).

LuaJIT is open-source, więc możesz wybrać kilka wskazówek, jeśli wybierzesz tę trasę. Alternatywnie możesz zajrzeć do źródła naprzód (którego twórca opracował the principle, którego próbujesz użyć), jeśli istnieje kompilacja MSVC, możesz zobaczyć, w jaki sposób to zrobili, w przeciwnym razie utkniesz w GCC (który jest isn ' t to zła rzecz, działa na wszystkich głównych platformach).

3

Wygląda na to, że można po prostu przenieść rzeczywisty kod do funkcji zamiast etykiet przypadku. Kod bajtowy można następnie przekształcić w prosty sposób w połączenia bezpośrednie. To znaczy. kod bajtu 1 tłumaczyłby się na CALL BC1. Ponieważ generujesz połączenia bezpośrednie, nie masz narzutu wskaźników funkcji. Rurociągi większości procesorów mogą śledzić takie bezwarunkowe bezpośrednie gałęzie.

W rezultacie rzeczywiste implementacje każdego kodu bajtowego są zoptymalizowane, a konwersja z kodu bajtowego na kod maszynowy jest trywialną konwersją 1: 1. Dostajesz trochę rozszerzenia kodu, ponieważ każdy CALL ma 5 bajtów (zakładając x86-32), ale to raczej nie jest poważny problem.