Stół kontrolny

Ta prosta tabela sterująca kieruje przebiegiem programu zgodnie z wartością pojedynczej zmiennej wejściowej. Każdy wpis w tabeli zawiera możliwą wartość wejściową do przetestowania pod kątem równości (domniemaną) oraz odpowiednią procedurę do wykonania w kolumnie akcji. Nazwę podprogramu można zastąpić względnym numerem podprogramu, jeśli wskaźniki nie są obsługiwane

Tabele sterujące to tabele , które kontrolują przepływ sterowania lub odgrywają główną rolę w sterowaniu programem. Nie ma sztywnych zasad dotyczących struktury lub zawartości tabeli sterującej — jej cechą kwalifikującą jest zdolność do kierowania przepływem sterowania w pewien sposób poprzez „wykonanie” przez procesor lub tłumacza . Projektowanie takich tabel jest czasami określane jako projektowanie oparte na tabelach (chociaż zwykle odnosi się to do automatycznego generowania kodu z tabel zewnętrznych, a nie bezpośrednich tabel czasu wykonywania). W niektórych przypadkach tablice sterujące mogą być specyficzną implementacją programowania automatów opartego na maszynach skończonych . Jeśli istnieje kilka hierarchicznych poziomów tabeli sterującej, mogą one zachowywać się w sposób równoważny maszynom stanów UML

Tabele sterujące często mają osadzony odpowiednik wyrażeń warunkowych lub odwołań do funkcji , zwykle implikowany przez ich względną pozycję w kolumnie na liście skojarzeń . Tabele sterujące zmniejszają potrzebę ciągłego programowania podobnych struktur lub instrukcji programu. Dwuwymiarowy charakter większości tabel sprawia, że ​​są one łatwiejsze do przeglądania i aktualizowania niż jednowymiarowy charakter kodu programu.

W niektórych przypadkach można wyznaczyć osoby niebędące programistami do obsługi zawartości tabel sterujących. Na przykład, jeśli wyszukiwana fraza wprowadzona przez użytkownika zawiera określoną frazę, adres URL (adres internetowy) można przypisać w tabeli, która kontroluje miejsce, do którego trafia wyszukiwany użytkownik. Jeśli fraza zawiera słowo „spódnica”, tabela może przekierować użytkownika do „www.zakupy.example/catalogs/skirts”, czyli strony katalogu produktów ze spódnicami. (Przykładowy adres URL nie działa w praktyce). Takim stołem zamiast programistów może zarządzać personel marketingu.

Typowe użycie

Bardziej zaawansowane użycie

podobny do kodu bajtowego - ale zwykle z operacjami implikowanymi przez samą strukturę tabeli

Struktura tabeli

Tabele mogą mieć wiele wymiarów, o stałej lub zmiennej długości i są zwykle przenośne między platformami komputerowymi , wymagając jedynie zmiany interpretera, a nie samego algorytmu – którego logika jest zasadniczo zawarta w strukturze i zawartości tabeli. Struktura tabeli może być podobna do wielomapowej tablicy asocjacyjnej , w której wartość danych (lub kombinacja wartości danych) może być odwzorowana na jedną lub więcej funkcji do wykonania.

Tabele jednowymiarowe

W być może najprostszej implementacji tabela sterująca może czasami być jednowymiarową tabelą służącą do bezpośredniego tłumaczenia nieprzetworzonych wartości danych na odpowiednie przesunięcie podprogramu , indeks lub wskaźnik przy użyciu nieprzetworzonych wartości danych albo bezpośrednio jako indeksu do tablicy, albo wykonując kilka podstawowych działań arytmetycznych na danych. Można to osiągnąć w stałym czasie (bez wyszukiwania liniowego lub wyszukiwania binarnego przy użyciu typowej tabeli przeglądowej na tablicy asocjacyjnej ). W większości architektur można to osiągnąć za pomocą dwóch lub trzech instrukcji maszynowych – bez żadnych porównań i pętli. Technika ta jest znana jako „ trywialna funkcja skrótu ” lub, gdy jest używana specjalnie dla tabel rozgałęzień, „ podwójna wysyłka ”. Aby było to wykonalne, zakres wszystkich możliwych wartości danych musi być mały (np. wartość ASCII lub EBCDIC , które mają zakres szesnastkowy „00” – „FF”. Jeśli rzeczywisty zakres ma być mniejszy niż to, tablicę można skrócić do mniej niż 256 bajtów).

Tabela do tłumaczenia surowych wartości ASCII (A,D,M,S) na nowy indeks podprogramu (1,4,3,2) w stałym czasie przy użyciu tablicy jednowymiarowej

(luki w zakresie są w tym przykładzie pokazane jako „..”, co oznacza „wszystkie wartości szesnastkowe do następnego wiersza”. Pierwsze dwie kolumny nie są częścią tablicy)

ASCII Klątwa Szyk
zero 00 00
.. .. 00
@ 40 00
A 41 01
.. .. 00
D 44 04
.. .. 00
M 4D 03
.. .. 00
S 53 02

W programowaniu opartym na automatach i pseudokonwersacyjnym przetwarzaniu transakcji, jeśli liczba odrębnych stanów programu jest niewielka, można użyć zmiennej sterującej „gęstej sekwencji” do wydajnego dyktowania całego przepływu głównej pętli programu.

Dwubajtowa surowa wartość danych wymagałaby tabeli o minimalnym rozmiarze 65 536 bajtów – aby obsłużyć wszystkie możliwości wejściowe – przy jednoczesnym umożliwieniu zaledwie 256 różnych wartości wyjściowych. Jednak ta technika bezpośredniego tłumaczenia zapewnia niezwykle szybką weryfikację i konwersję do (względnego) wskaźnika podprogramu, jeśli heurystyka wraz z wystarczającą pamięcią o szybkim dostępie pozwala na jej użycie.

Tabele oddziałów

Tabela rozgałęzień to jednowymiarowa „tablica” ciągłych instrukcji rozgałęzień / skoków kodu maszynowego, które powodują wielokierunkowe rozgałęzienie do etykiety programu po rozgałęzieniu przez bezpośrednio poprzedzającą i indeksowaną gałąź. Czasami jest generowany przez optymalizujący kompilator w celu wykonania instrukcji switch – pod warunkiem, że zakres wejściowy jest mały i gęsty, z kilkoma przerwami (jak utworzono w poprzednim przykładzie tablicy) [1] .

Chociaż dość zwarte - w porównaniu z wielokrotnymi odpowiednikami instrukcji If - instrukcje rozgałęzień nadal mają pewną nadmiarowość, ponieważ kod operacji rozgałęzienia i maska ​​kodu warunku są powtarzane wraz z przesunięciami rozgałęzień. Tabele sterujące zawierające tylko przesunięcia do etykiet programów można skonstruować w celu przezwyciężenia tej nadmiarowości (przynajmniej w językach asemblera), a jednocześnie wymagając jedynie niewielkiego narzutu czasu wykonania w porównaniu z konwencjonalną tablicą rozgałęzień.

Tabele wielowymiarowe

Częściej tabelę sterującą można traktować jako tablicę prawdy lub wykonywalną („binarną”) implementację drukowanej tabeli decyzyjnej (lub drzewa tabel decyzyjnych na kilku poziomach). Zawierają (często dorozumiane) twierdzenia wraz z jednym lub kilkoma powiązanymi „działaniami”. Działania te są zwykle wykonywane przez ogólne lub niestandardowe podprogramy , które są wywoływane przez program „ interpreter ”. Interpreter w tym przypadku skutecznie działa jak maszyna wirtualna , która „wykonuje” wpisy w tablicy sterującej, a tym samym zapewnia wyższy poziom abstrakcji niż podstawowy kod interpretera.

Tabelę sterującą można skonstruować podobnie do zależnej od języka instrukcji switch , ale z dodatkową możliwością testowania kombinacji wartości wejściowych (za pomocą warunków logicznych AND / OR ) i potencjalnego wywoływania wielu podprogramów (zamiast tylko jednego zestawu wartości i etykiety „rozgałęzienia do” programu). (Konstrukcja instrukcji switch w każdym przypadku może nie być dostępna lub może mieć myląco różne implementacje w językach wysokiego poziomu ( HLL ). Dla porównania, koncepcja tabeli sterującej nie ma wewnętrznych zależności językowych, ale mimo to może być implementowana inaczej w zależności od dostępnych cechy definicji danych wybranego języka programowania).

Zawartość tabeli

Tablica sterująca zasadniczo uosabia „ istotę ” konwencjonalnego programu, pozbawioną składni języka programowania i komponentów zależnych od platformy (np. skondensowane” do jego zmiennych (np. input1), wartości (np. „A”, „S”, „M” i „D”) oraz tożsamości podprogramów (np. „Dodaj”, „odejmuj,...” lub #1, # 2,..). Struktura samej tabeli zazwyczaj implikuje zaangażowane (domyślne) operacje logiczne - takie jak „testowanie pod kątem równości”, wykonanie podprogramu i „następnej operacji” lub postępowanie zgodnie z domyślną sekwencją (zamiast wyraźnie określonych w instrukcjach programu - zgodnie z wymaganiami w innych paradygmatach programowania ).

Wielowymiarowa tabela sterująca zwykle zawiera co najmniej pary wartość/działanie i może dodatkowo zawierać operatory i informacje o typie , takie jak lokalizacja, rozmiar i format danych wejściowych lub wyjściowych, konwersja danych (lub inne niuanse przetwarzania) jest wymagane przed lub po przetwarzaniu (jeśli nie jest to już ukryte w samej funkcji). Tabela może, ale nie musi, zawierać indeksy lub względne lub bezwzględne wskaźniki do ogólnych lub dostosowanych prymitywów lub podprogramów do wykonania w zależności od innych wartości w „wierszu”.

Poniższa tabela dotyczy tylko „input1”, ponieważ w tabeli nie określono żadnego konkretnego wejścia.

warunki i działania implikowane przez strukturę

(domniemane) JEŻELI = (domniemane) wykonać
wartość działanie
wartość działanie

(To równoległe parowanie wartości i akcji ma podobieństwa do konstrukcji w programowaniu sterowanym zdarzeniami , a mianowicie „wykrywanie zdarzeń” i „obsługa zdarzeń”, ale bez (koniecznie) asynchronicznej natury samego zdarzenia)

Różnorodność wartości, które można zakodować w tabeli sterującej, w dużej mierze zależy od używanego języka komputerowego . Język asemblera zapewnia najszerszy zakres typów danych, w tym (dla akcji), opcję bezpośrednio wykonywalnego kodu maszynowego . Zazwyczaj tabela sterująca będzie zawierać wartości dla każdej możliwej pasującej klasy danych wejściowych wraz z odpowiednim wskaźnikiem do podprogramu akcji. Niektóre języki twierdzą, że nie obsługują wskaźników (bezpośrednio), ale mimo to mogąobsługiwaćindeks , którego można użyć do przedstawienia „względnego numeru podprogramu” w celu wykonania wykonania warunkowego , kontrolowanego przez wartość we wpisie w tabeli (np. do wykorzystania w zoptymalizowanym SWITCH instrukcja – zaprojektowana z zerowymi przerwami (tzn. multiway branch ) ).

Komentarze umieszczone nad każdą kolumną (lub nawet osadzona dokumentacja tekstowa) mogą sprawić, że tabela decyzyjna będzie „czytelna dla człowieka” nawet po „skondensowaniu” (zakodowaniu) do jej istoty (i nadal będzie zasadniczo zgodna z oryginalną specyfikacją programu – zwłaszcza jeśli wydrukowana przed rozpoczęciem kodowania tworzona jest tablica decyzyjna, wyliczająca każdą unikalną akcję). Wpisy w tabeli mogą również opcjonalnie zawierać liczniki do zbierania statystyk w czasie wykonywania w celu optymalizacji „w locie” lub późniejszej

Lokalizacja stołu

Tabele sterujące mogą znajdować się w pamięci statycznej , pamięci dyskowej , takiej jak plik płaski lub baza danych , lub mogą być częściowo lub całkowicie budowane dynamicznie w czasie inicjalizacji programu z parametrów (które same mogą znajdować się w tabeli). Aby uzyskać optymalną wydajność, tabela powinna być rezydująca w pamięci, gdy interpreter zaczyna z niej korzystać.

Interpreter i podprogramy

Interpreter może być napisany w dowolnym odpowiednim języku programowania, włączając język wysokiego poziomu . Odpowiednio zaprojektowany ogólny interpreter wraz z dobrze dobranym zestawem ogólnych podprogramów (zdolnych do przetwarzania najczęściej występujących prymitywów ) wymagałby dodatkowego konwencjonalnego kodowania tylko dla nowych niestandardowych podprogramów (oprócz określenia samej tabeli sterującej). Opcjonalnie interpreter może dotyczyć tylko niektórych dobrze zdefiniowanych sekcji kompletnego programu aplikacji (takich jak główna pętla sterowania ), a nie innych, „mniej warunkowych” sekcji (takich jak inicjalizacja programu, zakończenie itd.).

Interpreter nie musi być nadmiernie skomplikowany ani tworzony przez programistę z zaawansowaną wiedzą twórcy kompilatora i może być napisany tak, jak każdy inny program użytkowy – z wyjątkiem tego, że zwykle jest zaprojektowany z myślą o wydajności. Jego podstawową funkcją jest „wykonywanie” wpisów w tabeli jako zestawu „instrukcji”. Nie ma wymogu analizowania wpisów w tabeli sterującej i dlatego powinny one być zaprojektowane, w miarę możliwości, tak, aby były „gotowe do wykonania”, wymagając jedynie „podłączenia” zmiennych z odpowiednich kolumn do już skompilowanego ogólnego kodu tłumacz. Instrukcje programu są teoretycznie rozszerzalne w nieskończoność i stanowią (prawdopodobnie dowolne) wartości w tabeli, które mają znaczenie tylko dla tłumacza. Przepływ sterowania interpretera polega zwykle na sekwencyjnym przetwarzaniu każdego wiersza tabeli, ale może być modyfikowany przez określone działania we wpisach tabeli.

Te dowolne wartości można zatem zaprojektować z myślą o wydajności — wybierając wartości, które można wykorzystać jako bezpośrednie indeksy do wskaźników danych lub funkcji . W przypadku określonych platform/ języków można je specjalnie zaprojektować, aby zminimalizować długości ścieżek instrukcji przy użyciu wartości tabeli rozgałęzień lub nawet, w niektórych przypadkach, na przykład w kompilatorach JIT , składać się z „ fragmentów ” kodu maszynowego (lub wskaźników do nich) bezpośrednio wykonywalnych.

Podprogramy mogą być kodowane albo w tym samym języku, co sam tłumacz, albo w dowolnym innym obsługiwanym języku programu (pod warunkiem, że istnieją odpowiednie mechanizmy połączeń między językami). Wybór języka dla tłumacza i/lub podprogramów zwykle zależy od tego, jak bardzo musi być przenośny na różnych platformach . Może istnieć kilka wersji interpretera, aby zwiększyć przenośność tabeli sterującej. Wskaźnik podrzędnej tabeli sterującej może opcjonalnie zastąpić wskaźnik podprogramu standardowego w kolumnach „działania”, jeśli interpreter obsługuje tę konstrukcję, reprezentującą warunkowy „spadek” na niższy poziom logiczny, naśladując konwencjonalną strukturę programu strukturalnego .

Względy dotyczące wydajności

Na pierwszy rzut oka wydaje się, że użycie tablic sterujących znacznie zwiększa narzut programu , wymagając procesu interpretera przed wykonaniem instrukcji „natywnego” języka programowania. Jednak nie zawsze tak jest. Dzięki oddzieleniu (lub „hermetyzacji”) kodu wykonywalnego od logiki, jak przedstawiono w tabeli, można łatwiej ukierunkować go na najbardziej wydajne wykonywanie swojej funkcji. Najwyraźniej można tego doświadczyć w do obsługi arkuszy kalkulacyjnych — gdzie podstawowe oprogramowanie do obsługi arkuszy kalkulacyjnych w przejrzysty sposób konwertuje złożone logiczne „formuły” w najbardziej efektywny sposób, w jaki jest to możliwe, w celu wyświetlenia wyników.

Poniższe przykłady zostały wybrane częściowo w celu zilustrowania potencjalnego wzrostu wydajności, który może nie tylko znacznie zrekompensować dodatkowy poziom abstrakcji, ale także ulepszyć – co w przeciwnym razie mogłoby być – mniej wydajnym, trudniejszym w utrzymaniu i dłuższym kodzie. Chociaż podane przykłady dotyczą języka asemblera „niskiego poziomu” i języka C , można zauważyć, że w obu przypadkach potrzeba bardzo niewielu wierszy kodu, aby zaimplementować podejście oparte na tablicy sterującej, a mimo to można osiągnąć bardzo znaczący stały czas ulepszenia wydajności, redukują powtarzające się kodowanie źródłowe i poprawiają przejrzystość w porównaniu z rozwlekłymi konstrukcjami konwencjonalnego języka programowania. Zobacz także cytaty Donalda Knutha dotyczące tabel i wydajności rozgałęzień wielotorowych w tym artykule.

Przykłady tabel sterujących

Poniższe przykłady są arbitralne (i dla uproszczenia opierają się tylko na jednym wejściu), jednak intencją jest jedynie zademonstrowanie, w jaki sposób można uzyskać przepływ sterowania za pomocą tabel zamiast zwykłych instrukcji programu. Powinno być jasne, że tę technikę można łatwo rozszerzyć, aby radziła sobie z wieloma danymi wejściowymi, zwiększając liczbę kolumn lub wykorzystując wiele wpisów w tabeli (z opcjonalnymi i/lub operatorami). Podobnie, używając (hierarchicznych) „połączonych” tabel sterujących, można wykonać programowanie strukturalne (opcjonalnie przy użyciu wcięć, aby pomóc wyróżnić podrzędne tabele sterujące).

„CT1” to przykład tabeli sterującej, która jest prostą tabelą przeglądową . Pierwsza kolumna reprezentuje testowaną wartość wejściową (przez dorozumiany „JEŻELI wejście1 = x”), a jeśli PRAWDA, odpowiednia druga kolumna („akcja”) zawiera adres podprogramu do wykonania przez wywołanie ( lub skok do – podobny do instrukcji SWITCH ). W efekcie jest to oddział wielokierunkowy ze zwrotem (forma „ dynamicznej wysyłki ”). Ostatni wpis jest domyślnym przypadkiem, w którym nie znaleziono dopasowania.

CT1

wejście 1 wskaźnik
A -->Dodaj
S -> odejmij
M -> Pomnóż
D -> Podziel
? -->Domyślne

Dla języków programowania, które obsługują wskaźniki w strukturach danych obok innych wartości danych, powyższa tabela (CT1) może być wykorzystana do skierowania przepływu sterowania do odpowiednich podprogramów zgodnie z pasującą wartością z tabeli (bez kolumny wskazującej inaczej, zakłada się równość w ten prosty przypadek).

Przykład asemblera dla IBM/360 (maksymalny zakres adresów 16Mb) lub Z/Architecture

W tym pierwszym przykładzie nie podejmuje się żadnych prób optymalizacji wyszukiwania w kodzie, zamiast tego zastosowano prostą technikę wyszukiwania liniowego — wyłącznie w celu zilustrowania koncepcji i zademonstrowania mniejszej liczby linii źródłowych. Aby obsłużyć wszystkie 256 różnych wartości wejściowych, potrzebnych byłoby około 265 wierszy kodu źródłowego (głównie wpisy w tabeli jednowierszowej), podczas gdy wielokrotne „porównanie i rozgałęzienie” normalnie wymagałoby około 512 wierszy źródłowych (rozmiar pliku binarnego jest również w przybliżeniu zmniejszony o połowę , każdy wpis w tabeli wymaga tylko 4 bajtów zamiast około 8 bajtów dla serii instrukcji „porównaj natychmiast”/rozgałęzień (w przypadku większych zmiennych wejściowych oszczędność jest jeszcze większa).

 * ------------------ tłumacz --------------------------------------------- --------------* LM R14,R0,=A(4,CT1,N) Ustaw R14=4, R15 --> tabela i R0 = nie. wpisów w tabeli (N) TRY CLC INPUT1,0(R15) ********* Znaleziono wartość we wpisie w tabeli ?  BE ACTION * pętla * TAK, Załaduj wskaźnik rejestru do podprogramu z tabeli AR R15,R14 * * NIE, Wskaż następny wpis w CT1 dodając R14 (=4) BCT R0,TRY ********* Wróć, aż liczba się wyczerpie, a następnie przejdź przez .  domyślna akcja ... żadna z wartości w tabeli nie pasuje, zrób coś innego LA R15,4(R15) wskazuje na domyślny wpis (poza końcem tabeli) ACTION L R15,0(R15) pobiera wskaźnik do R15, skąd R15 wskazuje BALR R14,R15 Wykonaj podprogram ("CALL" i wróć) B END go zakończ ten program * ------------------ tabela sterująca -------- ------------------**** * |  ta kolumna dopuszczalnych wartości EBCDIC lub ASCII jest testowana „=” ze zmienną „input1” * |  |  ta kolumna to 3-bajtowy adres odpowiedniego podprogramu standardowego * vv   CT1  DC C'A',AL3(ADD) START tablicy sterowniczej (długość wpisu 4 bajty) DC C'S',AL3(SUBTRACT) DC C'M',AL3 (MULTIPLY) DC C'D',AL3(DIVIDE) N EQU (*-CT1)/4 liczba poprawnych wpisów w tabeli (długość całkowita / długość wpisu) DC C'?',AL3(DEFAULT) wpis domyślny – używany na przepuść, aby przechwycić wszystkie zmienne wejściowe INPUT1 DS C w tej zmiennej * ------------------ podprogramy -------------- ----------------------------* DODAJ podprogram CSECT nr 1 (pokazany tutaj jako osobny CSECT, ale może . alternatywnie być w linii kod) . instrukcja(e) dodania BR R14 return SUBTRACT CSECT podprogram #2 .  instrukcje odejmowania BR R14 return .  itp..  

poprawa wydajności tłumacza w powyższym przykładzie

Aby dokonać wyboru w powyższym przykładzie, średnia długość ścieżki instrukcji (z wyłączeniem kodu podprogramu) wynosi „4n/2 +3”, ale można ją łatwo zredukować, gdzie n = 1 do 64, do stałego czasu o długości ścieżki „5” z zerowymi porównaniami , jeśli najpierw wykorzystana zostanie 256-bajtowa tabela translacji do utworzenia bezpośredniego indeksu do CT1 z surowych danych EBCDIC. Gdzie n = 6, byłoby to równoważne tylko 3 sekwencyjnym instrukcjom porównania i rozgałęzienia. Jednak gdy n <= 64, średnio wymagałoby to około 13 razy mniej instrukcji niż przy użyciu wielokrotnych porównań. Gdzie n=1 do 256, średnio zużyłoby około 42 razy mniej instrukcji – ponieważ w tym przypadku wymagana byłaby jedna dodatkowa instrukcja (aby pomnożyć indeks przez 4).

Ulepszony interpreter (średnio do 26 razy mniej wykonywanych instrukcji niż w powyższym przykładzie, gdzie n=1 do 64 i do 13 razy mniej niż byłoby to potrzebne przy wielokrotnych porównaniach).

Aby obsłużyć 64 różne wartości wejściowe, wymaganych jest około 85 wierszy kodu źródłowego (lub mniej) (głównie jednowierszowe wpisy w tabeli), podczas gdy wielokrotne „porównanie i rozgałęzienie” wymagałoby około 128 wierszy (rozmiar pliku binarnego jest również prawie o połowę mniejszy pomimo dodatkowa tablica 256 bajtów wymagana do wyodrębnienia drugiego indeksu).

 * ------------------ tłumacz --------------------------------------------- --------------* SR R14,R14 ********* Ustaw R14=0 CALC IC R14,INPUT1 * calc * umieść bajt EBCDIC w bitach o kolejności lo (24– 31) R14 IC R14,CT1X(R14) * * użyj wartości EBCDIC jako indeksu w tabeli 'CT1X', aby uzyskać nowy indeks ZNALEZIONO L R15,CT1(R14) ********* pobierz wskaźnik do podprogramu standardowego za pomocą indeksu (0,4, 8 itd.) BALR R14,R15 Wykonaj podprogram („CALL” i powrót lub Default) B END go zakończ ten program * --------------- dodatkowe tablica translacji (EBCDIC --> tablica wskaźników INDEX) 256 bajtów ----* CT1X DC 12AL1(00,00,00,00,00,00,00,00,00,00,00,00,00,00, 00,00) 12 identycznych zestawów po 16 bajtów x'00 * reprezentujących X'00 – x'BF' DC AL1(00,  04  ,00,00,  16  ,00,00,00,00,00,00,00 ,00,00,00,00) ..x'C0' – X'CF' DC AL1(00,00,00,00,  12  ,00,00,00,00,00,00,00,00,00 ,00,00) ..x'D0' – X'DF' DC AL1(00,00,  08  ,00,00,00,00,00,00,00,00,00,00,00,00,00 ) ..x'E0' – X'EF' DC AL1(00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00) ..x 'F0' – X'FF' * asembler może być użyty do automatycznego obliczenia wartości indeksu i uczynienia wartości bardziej przyjaznymi dla użytkownika * (np. '04' można zastąpić symbolicznym wyrażeniem 'PADD-CT1' w tabeli CT1X powyżej ) * zmodyfikowany CT1 (dodano akcję domyślną, gdy indeks = 00, jednowymiarowy, pełny adres 31-bitowy)  CT1  DC A(DEFAULT) indeks =00 START tablicy sterującej (4-bajtowe stałe adresowe) PADD DC A(ADD) =04 PSUB DC A(ODEJMUJ) =08 PMUL DC A(MULTIPLY) =12 PDIV DC A(DIVIDE) =16 * reszta kodu pozostaje taka sama jak w pierwszym przykładzie 

Dalszy ulepszony interpreter (średnio do 21 razy mniej wykonanych instrukcji (gdzie n>=64) niż w pierwszym przykładzie i do 42 razy mniej niż byłoby to potrzebne przy użyciu wielokrotnych porównań).

Aby obsłużyć 256 różnych wartości wejściowych, wymagane byłoby około 280 wierszy kodu źródłowego lub mniej (głównie jednowierszowe wpisy w tabeli), podczas gdy wielokrotne „porównanie i rozgałęzienie” wymagałoby około 512 wierszy (rozmiar pliku binarnego jest również prawie o połowę mniejszy raz więcej).

 * ------------------ tłumacz --------------------------------------------- --------------* SR R14,R14 ********* Ustaw R14=0 CALC IC R14,INPUT1 * calc * umieść bajt EBCDIC w bitach o kolejności lo (24– 31) z R14 IC R14,CT1X(R14) * * użyj wartości EBCDIC jako indeksu w tabeli 'CT1X', aby uzyskać nowy indeks SLL R14,2 * *  pomnóż indeks przez 4 (dodatkowa instrukcja)  FOUND L R15,CT1(R14) * ******** uzyskaj wskaźnik do podprogramu używając indeksu (0,4, 8 itd.) BALR R14,R15 Wykonaj podprogram („CALL” i wróć lub Domyślnie) B END go zakończ ten program * -- ------------- dodatkowa tablica translacji (EBCDIC --> tablica wskaźników INDEX) 256 bajtów ----* CT1X DC 12AL1(00,00,00,00,00,00,00, 00,00,00,00,00,00,00,00,00) 12 identycznych zestawów po 16 bajtów x'00' * reprezentujących X'00 – x'BF' DC AL1(00,  01  ,00,00,  04  ,00,00,00,00,00,00,00,00,00,00,00) ..x'C0' – X'CF' DC AL1(00,00,00,00,  03  ,00, 00,00,00,00,00,00,00,00,00,00) ..x'D0' – X'DF' DC AL1(00,00,  02  ,00,00,00,00,00, 00,00,00,00,00,00,00,00) ..x'E0' – X'EF' DC AL1(00,00,00,00,00,00,00,00,00,00, 00,00,00,00,00,00) ..x'F0' – X'FF' * asembler może być użyty do automatycznego obliczenia wartości indeksu i uczynienia wartości bardziej przyjaznymi dla użytkownika * (np. '01' może zostać zastąpione wyrażeniem symbolicznym „PADD-CT1/4” w tabeli CT1X powyżej) * zmodyfikowany CT1 (indeks oparty teraz na 0,1,2,3,4 zamiast 0,4,8,12,16, aby umożliwić wszystkie 256 wariantów )  CT1  DC A(DEFAULT) indeks =00 START tablicy sterującej (4-bajtowe stałe adresowe) PADD DC A(ADD) =01 PSUB DC A(ODEJMUJ) =02 PMUL DC A(MULTIPLY) =03 PDIV DC A(DIVIDE) =04 * reszta kodu pozostaje taka sama jak w drugim przykładzie 

Przykład w języku C Ten przykład w języku C wykorzystuje dwie tabele, pierwsza (CT1) to prosta jednowymiarowa tabela przeglądowa z przeszukiwaniem liniowym – aby uzyskać indeks przez dopasowanie danych wejściowych (x), a druga, powiązana tabela (CT1p), to tabela adresów etykiet, do których można przejść.

                                                       
                      
     0          
              
                              static  const  char  CT1  []  =  {  "A"  ,  "S"  ,  "M"  ,  "D"  };  /* dozwolone wartości wejściowe */  static  const  void  *  CT1p  []  =  {  &&  Dodaj  ,  &&  Odejmij  ,  &&  Pomnóż  ,  &&  Podziel  ,  &&  Domyślnie  };  /* etykiety do goto & default*/  for  (  int  i  =  ;  i  <  sizeof  (  CT1  );  i  ++  )  /* pętla przez wartości ASCII */  {  if  (  x  ==  CT1  [  i  ])  goto  *  CT1p  [  i  ];  }  /* znaleziono --> odpowiednia etykieta */  goto  *  CT1p  [  i  +  1  ];  /* nie znaleziono --> etykieta domyślna */ 

Można to usprawnić, jeśli do translacji nieprzetworzonej wartości ASCII (x) bezpośrednio na gęstą sekwencyjną wartość indeksu używaną do bezpośredniego zlokalizowania adresu rozgałęzienia z CT1p zostanie użyta tablica 256-bajtowa ( tj . szyk). Będzie wtedy wykonywany w stałym czasie dla wszystkich możliwych wartości x (Gdyby CT1p zawierał nazwy funkcji zamiast etykiet, skok można by zastąpić dynamicznym wywołaniem funkcji, eliminując podobne do przełącznika goto – ale zmniejszając wydajność o dodatkowy koszt funkcji sprzątania ).

          
 
    
                            
                            
                            
                            
                            
                            
                            
                            
                            
                            
                            
                            
                            
                            
                            
                            
 
               
             static  const  void  *  CT1p  []  =  {  &&  Domyślnie  ,  &&  Dodaj  ,  &&  Odejmij  ,  &&  Pomnóż  ,  &&  Podziel  };  /* 256-bajtowa tabela poniżej zawiera wartości (1,2,3,4) w odpowiednich pozycjach ASCII (A,S,M,D), wszystkie inne ustawione na 0x00 */ static  const  char  CT1x  [  ]  =  {  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x01'  ,  '\x00'  ,  '\x00'  ,  '\x04'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '  \x00'  ,  '\x03' ,   '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x02'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x03'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  , '\x00'   ,  '\x00'  ,  '  \x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  ,  '\x00'  };  /* poniższy kod będzie wykonywany w stałym czasie, niezależnie od wartości wprowadzonego znaku (x) */  i  =  CT1x  (  x  );  /* wyodrębnienie prawidłowego indeksu podprogramu z tabeli CT1x, używając jego wartości ASCII jako indeksu na początku */  goto  *  CT1p  [  i  ];  /* goto (Przełącz na) etykietę odpowiadającą indeksowi (0=domyślny, 1=Dodaj, 2=Odejmij,.) - patrz CT1p */ 

Poniższy przykład ilustruje, jak podobny efekt można osiągnąć w językach, które nie obsługują definicji wskaźników w strukturach danych, ale obsługują indeksowane rozgałęzienia do podprogramu — zawartego w ( opartej na 0 ) tablicy wskaźników podprogramu. Tabela (CT2) służy do wyodrębnienia indeksu (z drugiej kolumny) do tablicy wskaźników (CT2P). Jeśli tablice wskaźników nie są obsługiwane, można użyć instrukcji SWITCH lub jej odpowiednika, aby zmienić przepływ sterowania na jedną z sekwencji etykiet programu (np.: case0, case1, case2, case3, case4), które następnie albo bezpośrednio przetwarzają dane wejściowe, albo w przeciwnym razie wykonaj wywołanie (z powrotem) do odpowiedniego podprogramu (domyślnie, dodaj, odejmuj, pomnóż lub podziel, ..), aby sobie z tym poradzić.

CT2

wejście 1 subr #
A 1
S 2
M 3
D 4
? 0

Podobnie jak w powyższych przykładach, możliwe jest bardzo wydajne przetłumaczenie potencjalnych wartości wejściowych ASCII (A,S,M,D lub nieznane) na indeks tablicy wskaźników bez faktycznego korzystania z wyszukiwania w tabeli, ale jest to pokazane tutaj jako tabela dla zachowania spójności z pierwszy przykład.

CT2P tablica wskaźników
tablica wskaźników
-->domyślnie
-->dodaj
-->odejmuj
-->mnóż
-->podziel
-->?inne

Wielowymiarowe tabele kontrolne można konstruować (tj. dostosowywać), które mogą być „bardziej złożone” niż powyższe przykłady, które mogą testować wiele warunków na wielu danych wejściowych lub wykonywać więcej niż jedno „działanie” w oparciu o pewne kryteria dopasowania. „Akcja” może zawierać wskaźnik do innej podrzędnej tabeli sterującej. Poniższy prosty przykład zawiera niejawny warunek „LUB” jako dodatkową kolumnę (aby obsłużyć wprowadzanie małych liter, jednak w tym przypadku można to równie dobrze obsłużyć, umieszczając dodatkowy wpis dla każdego z małych liter określający ten sam identyfikator podprogramu co wielkie litery). Dołączona jest również dodatkowa kolumna do zliczania rzeczywistych zdarzeń w czasie wykonywania dla każdego wejścia w miarę ich występowania.

CT3

wejście 1 alternatywny subr # liczyć
A A 1 0
S S 2 0
M M 3 0
D D 4 0
? ? 0 0

Wpisy w tabeli sterującej są wtedy znacznie bardziej podobne do instrukcji warunkowych w językach proceduralnych , ale, co najważniejsze, bez rzeczywistych (zależnych od języka) instrukcji warunkowych (tj. instrukcji) (ogólny kod znajduje się fizycznie w interpreterze, który przetwarza wpisy w tabeli, a nie w samej tabeli – która po prostu ucieleśnia logikę programu poprzez swoją strukturę i wartości).

W tablicach takich jak ta, gdzie seria podobnych wpisów w tablicy definiuje całą logikę, numer wpisu w tablicy lub wskaźnik może skutecznie zastąpić licznik programu w bardziej konwencjonalnych programach i może zostać wyzerowany w „akcji”, również określonej w wpis w tabeli. Poniższy przykład (CT4) pokazuje, w jaki sposób rozszerzenie wcześniejszej tabeli w celu uwzględnienia „następnego” wpisu (i/lub włączenia podprogramu „zmień przepływ” ( skok )) może utworzyć pętlę (Ten przykład nie jest w rzeczywistości najskuteczniejszym sposobem skonstruować taką tabelę kontrolną, ale demonstrując stopniową „ewolucję” z pierwszych przykładów powyżej, pokazuje, w jaki sposób można użyć dodatkowych kolumn do modyfikacji zachowania.) Piąta kolumna pokazuje, że więcej niż jedno działanie można zainicjować jednym wpisem w tabeli – w tym przypadku czynność, która ma zostać wykonana po normalnym przetworzeniu każdego wpisu (wartości „-” oznaczają „brak warunków” lub „brak działania”).

Programowanie strukturalne lub kod „bez Goto” (zawierający odpowiedniki konstrukcji „ DO WHILE ” lub „ pętli for ”) można również dostosować do odpowiednio zaprojektowanych i „wciętych” struktur tabel sterujących.

CT4 (kompletny „program” do odczytu danych wejściowych 1 i przetwarzania, powtarzany do napotkania „E”)

wejście 1 alternatywny subr # liczyć skok
- - 5 0 -
mi mi 7 0 -
A A 1 0 -
S S 2 0 -
M M 3 0 -
D D 4 0 -
? ? 0 0 -
- - 6 0 1
Tablica wskaźników CT4P
tablica wskaźników
-->Domyślne
-->Dodaj
-->Odejmuj
-->Mnóż
-->Podziel
-->Odczyt danych wejściowych1
-->Zmień przepływ
-->Koniec

Ocena oparta na tabeli

W specjalistycznej dziedzinie ratingów telekomunikacyjnych (dotyczących określania kosztu konkretnego połączenia) techniki oceniania oparte na tabelach ilustrują wykorzystanie tabel kontrolnych w aplikacjach, w których zasady mogą często się zmieniać z powodu sił rynkowych. Tabele określające opłaty mogą w wielu przypadkach zostać zmienione w krótkim czasie przez osoby niebędące programistami.

Jeśli algorytmy nie są wstępnie wbudowane w interpreter (i dlatego wymagają dodatkowej interpretacji wyrażenia przechowywanego w tabeli w czasie wykonywania), jest to raczej „ocena oparta na regułach” niż ocena oparta na tabeli (i w konsekwencji zużywa znacznie więcej narzutów ).

Arkusze kalkulacyjne

Arkusz danych arkusza kalkulacyjnego można traktować jako dwuwymiarową tabelę sterującą, w której niepuste komórki reprezentują dane dla bazowego programu arkusza kalkulacyjnego (interpretera). Komórki zawierające formułę są zwykle poprzedzone znakiem równości i po prostu oznaczają specjalny typ wprowadzania danych, który dyktuje przetwarzanie innych komórek, do których istnieją odniesienia – poprzez zmianę przepływu sterowania w interpreterze. To eksternalizacja formuł z podstawowego interpretera, który wyraźnie identyfikuje zarówno arkusze kalkulacyjne, jak i cytowany powyżej przykład „oceny opartej na regułach” jako łatwe do zidentyfikowania przypadki użycia tabel sterujących przez osoby niebędące programistami.

Paradygmat programowania

Jeśli można by powiedzieć, że technika tablic sterujących należy do jakiegoś konkretnego paradygmatu programowania , najbliższą analogią mogłoby być programowanie oparte na automatach lub „refleksyjne” (forma metaprogramowania – ponieważ można by powiedzieć, że wpisy w tablicy „modyfikują” zachowanie interpretator). Jednak sam interpreter i podprogramy można zaprogramować przy użyciu dowolnego z dostępnych paradygmatów, a nawet ich kombinacji. Sama tabela może być zasadniczo zbiorem wartości „ surowych danych ”, których nawet nie trzeba kompilować i które można odczytać z zewnętrznego źródła (z wyjątkiem specyficznych, zależnych od platformy implementacji wykorzystujących bezpośrednio wskaźniki pamięci dla większej wydajności).

Analogia do kodu bajtowego / zestawu instrukcji maszyny wirtualnej

Wielowymiarowa tabela sterująca ma pewne koncepcyjne podobieństwa do kodu bajtowego działającego na maszynie wirtualnej , ponieważ do wykonania rzeczywistego wykonania zwykle wymagany jest zależny od platformy program „interpretujący” (który jest w dużej mierze warunkowo określony przez zawartość tabel). Istnieją również pewne koncepcyjne podobieństwa do niedawnego wspólnego języka pośredniego (CIL) w celu stworzenia wspólnego pośredniego „zestawu instrukcji”, który jest niezależny od platformy (ale w przeciwieństwie do CIL, nie ma pretensji do wykorzystania jako wspólnego zasobu dla innych języków) . Kod P można również uznać za podobną, ale wcześniejszą implementację, której początki sięgają 1966 roku.

Pobieranie instrukcji

Kiedy wielowymiarowa tabela sterująca jest używana do określenia przebiegu programu, normalna „sprzętowa” funkcja licznika programów jest skutecznie symulowana za pomocą wskaźnika do pierwszego (lub następnego) wpisu w tabeli lub indeksu do niego. „Pobieranie” instrukcji obejmuje dekodowanie danych w tym wpisie tabeli - bez konieczności wcześniejszego kopiowania wszystkich lub niektórych danych we wpisie. Języki programowania, które są w stanie używać wskaźników, mają podwójną zaletę polegającą na mniejszym obciążeniu , zarówno w dostępie do zawartości, jak i przesuwaniu licznika, aby wskazywał następny wpis w tabeli po wykonaniu. Obliczenie adresu następnej „instrukcji” (tj. wpisu w tablicy) może być nawet wykonane jako opcjonalna akcja dodatkowa każdego pojedynczego wpisu w tablicy, pozwalająca na pętle i/lub instrukcje skoków na dowolnym etapie.

Monitorowanie wykonania tabeli sterującej

Program interpretera może opcjonalnie zapisać licznik programu (i inne istotne szczegóły w zależności od typu instrukcji) na każdym etapie, aby zarejestrować pełne lub częściowe śledzenie rzeczywistego przebiegu programu do celów debugowania, wykrywania gorących punktów, analizy pokrycia kodu i analizy wydajności ( zob . przykłady CT3 i CT4 powyżej).

Zalety

  • przejrzystość — tabele informacyjne wszechobecne i w większości z natury zrozumiałe nawet dla ogółu społeczeństwa (zwłaszcza tabele diagnostyki usterek w przewodnikach po produktach )
  • przenośność – może być zaprojektowana tak, aby była w 100% niezależna od języka (i niezależna od platformy – z wyjątkiem tłumacza)
  • elastyczność – możliwość przejrzystego wykonywania prymitywów lub podprogramów i dostosowywania ich do problemu
  • zwięzłość – tabela zwykle pokazuje parowanie warunek/akcja obok siebie (bez typowych zależności implementacji platformy/języka), często również skutkujące
    • plik binarny – zmniejszony rozmiar dzięki mniejszej ilości powielanych instrukcji
    • źródłowy – zmniejszony rozmiar poprzez eliminację wielu instrukcji warunkowych
    • ulepszone prędkości ładowania (lub pobierania) programów
  • łatwość konserwacji – tabele często zmniejszają liczbę wierszy źródłowych potrzebnych do utrzymania w porównaniu z wielokrotnymi porównaniami
  • lokalizacja odniesienia – zwarte struktury tabel powodują, że tabele pozostają w pamięci podręcznej
  • ponowne użycie kodu – „tłumacz” jest zwykle wielokrotnego użytku. Często można go łatwo dostosować do nowych zadań programistycznych przy użyciu dokładnie tej samej techniki i może rosnąć „organicznie”, stając się w efekcie standardową biblioteką wypróbowanych i przetestowanych podprogramów kontrolowanych przez definicje tabel.
  • wydajność – możliwa optymalizacja całego systemu. Każda poprawa wydajności interpretera zwykle poprawia wszystkie korzystające z niego aplikacje (patrz przykłady w „CT1” powyżej).
  • rozszerzalny – można dodawać nowe „instrukcje” – po prostu rozszerzając interpreter
  • interpreter może być napisany jak program użytkowy

Opcjonalnie:-

  • interpreter może być introspekcyjny i „ samooptymalizujący ” przy użyciu metryk czasu wykonywania zebranych w samej tabeli (patrz CT3 i CT4 – z wpisami, które można okresowo sortować według malejącej liczby). Interpreter może również opcjonalnie wybrać najbardziej wydajną technikę wyszukiwania dynamicznie na podstawie metryk zebranych w czasie wykonywania (np. rozmiar tablicy, zakres wartości, posortowane lub nieposortowane)
  • dynamiczna wysyłka – wspólne funkcje mogą być wstępnie ładowane, a mniej popularne funkcje pobierane tylko przy pierwszym kontakcie, aby zmniejszyć zużycie pamięci . Aby to osiągnąć, można zastosować zapamiętywanie w tabeli .
  • Interpreter może mieć wbudowane funkcje debugowania, śledzenia i monitorowania – które można następnie dowolnie włączać i wyłączać zgodnie z trybem testowym lub „na żywo”
  • tablice kontrolne mogą być budowane „w locie” (zgodnie z pewnymi danymi wprowadzonymi przez użytkownika lub na podstawie parametrów), a następnie wykonywane przez interpreter (dosłownie bez budowania kodu).

Niedogodności

  • wymóg szkolenia – programiści aplikacji zwykle nie są szkoleni w zakresie tworzenia ogólnych rozwiązań

Poniższe informacje dotyczą głównie ich użycia w tabelach wielowymiarowych, a nie omówionych wcześniej tabel jednowymiarowych.

  • narzut – pewien wzrost z powodu dodatkowego poziomu pośrednictwa spowodowanego koniecznością „interpretacji” wirtualnych instrukcji (zwykle można to jednak z nawiązką zrekompensować dobrze zaprojektowanym generycznym interpreterem, który w pełni wykorzystuje wydajne techniki bezpośredniego tłumaczenia, wyszukiwania i testowania warunkowego, które mogą nie zostały wykorzystane w inny sposób)
  • Złożonych wyrażeń nie zawsze można używać bezpośrednio we wpisach tabeli danych do celów porównawczych
(te „wartości pośrednie” można jednak obliczyć wcześniej zamiast tego w ramach podprogramu standardowego, a ich wartości można przywołać w warunkowych wpisach w tabeli. Alternatywnie podprogram standardowy może wykonać pełny złożony test warunkowy (jako „działanie” bezwarunkowe) i ustawiając flaga prawdy jako wynik, może być następnie przetestowana w następnym wpisie w tabeli (patrz Twierdzenie o programie strukturalnym )

Cytaty

Rozgałęzienia wielokierunkowe to ważna technika programowania, która zbyt często jest zastępowana nieefektywną sekwencją testów if. Peter Naur napisał mi niedawno, że uważa wykorzystanie tabel do kontrolowania przebiegu programu za podstawową ideę informatyki, która została prawie zapomniana; ale spodziewa się, że lada dzień będzie gotowy do ponownego odkrycia. Jest to klucz do wydajności we wszystkich najlepszych kompilatorach, które studiowałem.

Donald Knuth , Programowanie strukturalne z przejściem do instrukcji

Istnieje inny sposób spojrzenia na program napisany w języku interpretacyjnym. Można to traktować jako serię wywołań podprogramu, jedno po drugim. Taki program może w rzeczywistości zostać rozszerzony do długiej sekwencji wywołań podprogramów i odwrotnie, taka sekwencja może być zwykle spakowana w zakodowaną postać, którą łatwo zinterpretować. Zaletą technik interpretacyjnych jest zwięzłość reprezentacji, niezależność od maszyny i zwiększone możliwości diagnostyczne. Interpreter często można napisać w taki sposób, że ilość czasu spędzonego na interpretacji samego kodu i przejściu do odpowiedniej procedury jest znikoma

Donald Knuth , Sztuka programowania komputerowego, tom 1, 1997, strona 202

Przestrzeń wymaganą do przedstawienia programu można często zmniejszyć, stosując interpretery, w których typowe sekwencje operacji są reprezentowane zwięźle. Typowym przykładem jest użycie maszyny skończonej do zakodowania złożonego protokołu lub formatu leksykalnego w małej tabeli

Jon Bentley , Pisanie wydajnych programów

Tabele skoków mogą być szczególnie wydajne, jeśli można pominąć testy zasięgu. Na przykład, jeśli wartość kontrolna jest typem wyliczeniowym (lub znakiem), może zawierać tylko mały stały zakres wartości, a test zakresu jest zbędny, pod warunkiem, że tabela skoków jest wystarczająco duża, aby obsłużyć wszystkie możliwe wartości

Dawid A. SPULER, generowanie kodu kompilatora dla instrukcji Multiway Branch jako problem wyszukiwania statycznego

Programy muszą być pisane, aby ludzie je czytali, a tylko przypadkowo, aby maszyny mogły je wykonywać.

- „Struktura i interpretacja programów komputerowych”, przedmowa do pierwszego wydania, Abelson & Sussman

Pokaż mi swój schemat blokowy i ukryj swoje tabele, a nadal będę zdumiony. Pokaż mi swoje tabele, a zwykle nie będę potrzebował twojego schematu blokowego; to będzie oczywiste.

- „Mityczny miesiąc osobowy: eseje o inżynierii oprogramowania”, Fred Brooks

Zobacz też

Notatki

Linki zewnętrzne