2015-12-24 35 views
9

W języku Haskell istnieje funkcja języka o nazwie "as" -operator (czasami nazywana aliasem). Idea jest następująca: podany masz funkcję, na przykład potrzebny jako wejście listę i chce powrócić wszystkie ogony można zaimplementować to jako:Czy Prolog ma alias "operator", taki jak Haskell?

tails [email protected](_:xs) = a : tails xs 
tails [] = [[]] 

The @ zapewnia, że ​​masz odniesienie zarówno do cały argument, jak również odniesienie do niektórych części struktury parametru. Jest to inteligentne pod względem wydajności (jest bardziej hackem wydajnościowym, ponieważ rekonstruowanie tablicy (x:xs)) w ciele pierwszej linii, jeśli nie zostanie zoptymalizowane przez kompilator, spowoduje przydzielenie nowego obiektu, modyfikowanie pól itd. Zobacz here, aby uzyskać więcej informacji.

Zastanawiałem się, czy Prolog ma coś odpowiednik: na przykład, jeśli chcesz wprowadzić ogony w Prologu, można to zrobić w następujący sposób:

tails([H|T],[[H|T]|TA]) :- 
    tails(T,TA). 
tails([],[[]]). 

Ale to może być bardziej skuteczne, jeśli nie było " jako "-operator jak:

tails([email protected][_|T],[L|TA]) :- %This does not compile 
    tails(T,TA). 
tails([],[[]]). 

Czy istnieje taka konstrukcja lub rozszerzenie języka?

+2

W prologu można uniknąć aliasu. Po prostu użyj 'ogona (L, [L | TA]): - L = [_ | T], ...'. – Bakuriu

+0

Zgadzam się, ale byłoby miło, gdyby to było możliwe w głowie. (Wiem, że denerwuję: S) –

Odpowiedz

8

TL; DR: Dobry pomysł ! Przyspieszenie wydaje się być ograniczone do ~ 20% (w przypadku większości rozmiarów list).

W tej odpowiedzi, porównujemy trzy różne predykaty, które różnią się w zakresie ponownego wykorzystania @ -jak danych:

 
list_tails([], [[]]).    % (1) like `tails/2` given by the OP ... 
list_tails([E|Es], [[E|Es]|Ess]) :- %  ....... but with a better name :-) 
    list_tails(Es, Ess). 

list_sfxs1(Es, [Es|Ess]) :-   % (2) "re-use, mutual recursion" 
    aux_list_sfxs1(Es, Ess).   %  "sfxs" is short for "suffixes" 

aux_list_sfxs1([], []). 
aux_list_sfxs1([_|Es], Ess) :- 
    list_sfxs1(Es, Ess). 

list_sfxs2([], [[]]).    % (3) "re-use, direct recursion" 
list_sfxs2(Es0, [Es0|Ess]) :- 
    Es0 = [_|Es], 
    list_sfxs2(Es, Ess). 

do pomiaru czasu pracy, możemy użyć następującego kodu:

 
:-( dif (D, sicstus), current_prolog_flag (dialect ,D) 
    ; use_module (library(between)) 
). 

run_benchs(P_2s, P_2, L, N, T_ms) :- 
    between (1, 6, I), 
    L  is 10 ^ I, 
    N is 10^(8-I), 
    length (Xs, L), 
    member (P_2, P_2s), 
    garbage_collect , 
    call_walltime(run_bench_core(P_2,Xs,N), T_ms). 

run_bench_core(P_2, Xs, N) :- 
    between(1, N, _), 
    call (P_2, Xs, _), 
    false . 
run_bench_core(_, _, _). 

do pomiaru , używamy call_ walltime /2 -odmiany call_time/2:

 
call_walltime(G, T_ms) :- 
    statistics (walltime , [T0|_]), 
    G, 
    statistics(walltime, [T1|_]), 
    T_ms is T1 - T0. 

Postawmy powyższe warianty kodu do testu ...

  • ... stosując inną listę długości L ...
  • ... i działa każdy test kilka razy (dla N lepsza dokładność).

Po pierwsze, używamy wersji 7.03.14 (64-bitowy):

 
?- run_benchs([list_sfxs1,list_sfxs2,list_tails], P_2, L, N, T_ms). 
    P_2 = list_sfxs1, L*N = 10*10000000, T_ms = 7925 
; P_2 = list_sfxs2, L*N = 10*10000000, T_ms = 7524 
; P_2 = list_tails, L*N = 10*10000000, T_ms = 6936 
; 
    P_2 = list_sfxs1, L*N = 100*1000000, T_ms = 6502 
; P_2 = list_sfxs2, L*N = 100*1000000, T_ms = 5861 
; P_2 = list_tails, L*N = 100*1000000, T_ms = 5618 
; 
    P_2 = list_sfxs1, L*N = 1000*100000, T_ms = 6434 
; P_2 = list_sfxs2, L*N = 1000*100000, T_ms = 5817 
; P_2 = list_tails, L*N = 1000*100000, T_ms = 9916 
; 
    P_2 = list_sfxs1, L*N = 10000*10000, T_ms = 6328 
; P_2 = list_sfxs2, L*N = 10000*10000, T_ms = 5688 
; P_2 = list_tails, L*N = 10000*10000, T_ms = 9442 
; 
    P_2 = list_sfxs1, L*N = 100000*1000, T_ms = 10255 
; P_2 = list_sfxs2, L*N = 100000*1000, T_ms = 10296 
; P_2 = list_tails, L*N = 100000*1000, T_ms = 14592 
; 
    P_2 = list_sfxs1, L*N = 1000000*100, T_ms = 6955 
; P_2 = list_sfxs2, L*N = 1000000*100, T_ms = 6534 
; P_2 = list_tails, L*N = 1000000*100, T_ms = 9738. 

Następnie powtarzamy poprzedni zapytanie korzystając wersji 4.3.2 (64 bitów)

 
?- run_benchs([list_sfxs1,list_sfxs2,list_tails], P_2, L, N, T_ms). 
    P_2 = list_sfxs1, L*N = 10*10000000, T_ms = 1580 
; P_2 = list_sfxs2, L*N = 10*10000000, T_ms = 1610 
; P_2 = list_tails, L*N = 10*10000000, T_ms = 1580 
; 
    P_2 = list_sfxs1, L*N = 100*1000000, T_ms = 710 
; P_2 = list_sfxs2, L*N = 100*1000000, T_ms = 750 
; P_2 = list_tails, L*N = 100*1000000, T_ms = 840 
; 
    P_2 = list_sfxs1, L*N = 1000*100000, T_ms = 650 
; P_2 = list_sfxs2, L*N = 1000*100000, T_ms = 660 
; P_2 = list_tails, L*N = 1000*100000, T_ms = 740 
; 
    P_2 = list_sfxs1, L*N = 10000*10000, T_ms = 620 
; P_2 = list_sfxs2, L*N = 10000*10000, T_ms = 650 
; P_2 = list_tails, L*N = 10000*10000, T_ms = 740 
; 
    P_2 = list_sfxs1, L*N = 100000*1000, T_ms = 670 
; P_2 = list_sfxs2, L*N = 100000*1000, T_ms = 650 
; P_2 = list_tails, L*N = 100000*1000, T_ms = 750 
; 
    P_2 = list_sfxs1, L*N = 1000000*100, T_ms = 12610 
; P_2 = list_sfxs2, L*N = 1000000*100, T_ms = 12560 
; P_2 = list_tails, L*N = 1000000*100, T_ms = 33460. 

Podsumowanie:

  • ps-rodzaju lasera puszki i nie poprawia znacznie wydajność.
  • W powyższych testach, SICStus Prolog daje 10X przyspieszenie, w porównaniu do SWI-Prolog!

Przypis 1: Dlaczego stunt oddania (@)/2 w głowie regułę? Aby skończyć z non-idiomatic kodu Prolog?
Przypis 2: Interesuje nas całkowity czas wykonywania. Czemu? Ponieważ koszty zbierania śmieci są wyświetlane przy większych rozmiarach danych!
Przypis 3: Sekwencja odpowiedzi została poddana obróbce końcowej ze względu na czytelność.
Przypis 4: Dostępne od release 4.3.0. Obecne architektury docelowe obejmują IA-32 i AMD64.

+0

Zastanawiam się, czy umieszczenie czegoś takiego w głowie nie może prowadzić do dodatkowych optymalizacji. Dla 'ogonów', nie jest to zbyt użyteczne, ale teraz odkładasz sprawdzanie, które * mogło * zostać wykonane przed wywołaniem tego predykatu. Niemniej imponująca odpowiedź +1. –

+0

Co więcej, zastanawiam się, czy nie można poprawić kompilatora, aby zauważyć, że '[E | Es]' jest używane dwa razy iw ten sposób konstruować sam "niejawny" alias. –

+3

@WillemVanOnsem. Tak, w zasadzie kompilatory Prolog * mogą * to robić. OTOH w czysto funkcjonalnym języku z leniwą oceną (jak Haskell) jest to jeszcze prostsze. Kompilacja Prolog jest trudna, jeśli chcesz się upewnić, że skompilowany i analogowy kod interpretowany nigdy ** nigdy nie zachowuje się inaczej. Mnóstwo szczegółów/szczegółów musi być dobrze załatwionych. SICStus JIT jest stosunkowo nowy ... i bardzo imponujący! – repeat