110 likes | 247 Views
Prolog Program Style ( ch . 8). Many style issues are applicable to any program in any language. Some Prolog specifics: short clauses, predicates If meaning in comments must use “and”, then break into separate preds . meaningful names for predicates, constants , variables
E N D
Prolog Program Style (ch. 8) • Many style issues are applicable to any program in any language. • Some Prolog specifics: • short clauses, predicates • If meaning in comments must use “and”, then break into separate preds. • meaningful names for predicates, constants, variables • helps programmer understand the meaning of their code • good indentation • use the same conventions throughout program • cuts: use green cuts rather than red • if possible, use not, one, -> instead of cuts • try to avoid assert/retract unless absolutely required • be careful to restore program state (database) when necessary • comments: • I/O convention of predicates • essentials of algorithm • special features, shortcomings, assumptions about input, ...
Efficiency issues • Declarative vs procedural • a difficult balance: clearest solutions can be the most inefficient • sometimes must encode a clear solution efficiently, using procedural considerations • Algorithm vs implementation • a quicksort is faster than a bubble sort, no matter how you implement it! • A fast algorithm can be implemented inefficiently, however. • Data structures • related to algorithms • a better data structure can naturally result in a faster implementation • eg. a sorted binary tree permits faster data extraction than an unsorted linear list • Nondeterminism vs determinism • reduce backtracking: avoid needless searching: once, if-then-else, cuts • Backtracking requires extra memory. • consult vs compile • depending on implementation, compiling programs can give upwards of a 10x speed increase • (lose debugging info, however)
Memory and recursion • recursion requires memory • stack used to save tree for return • extra usage for backtracking... p(...) :- do_things(...), % Memory used here to do_more_things(...), % save tree for backtracking. p(...). % Recursion. • This is an examples of tail recursion: last goal is recursive call, and goals before it are deterministic (backtracking will fail). • To optimize, do this: p(...) :- do_things(...), do_more_things(...), !, % This throws away tree before call. p(...). % Recursion.
Efficiency: memory • This can still cause problems in very large computations. Sometimes, various memory usage before the cut can be enormous. • Some Prologs such as Sicstus have a builtin memory management optimizer called “garbage_collect” • garbage collection important for symbolic languages (and Java!): finds unused memory (eg. unused atoms, freed clauses, etc.), and puts it back on heap for later usage • done automatically at periodic times. Unfortunately, might not happen when required! • calling garbage_collect lets programmer ensure of optimal memory use p(...) :- do_things(...), garbage_collect, do_more_things(...), garbage_collect, !, % this throws away tree before call p(...). % recursion
Last-clause determinacy COSC 2P93 Prolog: Lisp • Sicstus compiler will automatically “insert” a cut into the first goal of the last clause of a recursive predicate. • So this is not required... p(...) :- ... p(...) :- % last clause of p. !, % unnecessary! (do something), p(...).
Efficiency: execution profiling Profile: run-time execution statistics See sect. 9.2, Sicstus manual Scheme: ?- [Load some code.] ?- prolog_flag(profiling,_,on). ?- [Run some queries.] ?- prolog_flag(profiling,_,off). ?- print_profile.
Profiling COSC 2P93 Prolog: Lisp ?- print_profile. insns try/retry called name ---------------------------------------------------------------- *13694/14300 user:remove_nth/4 ← callers: remove_nth 13694 times *606/14300 user:select_random/3 ← select_random 606 times 97676 606 *14300 user:remove_nth/4 ← remove_nth called 14300 times *13694/14300 user:remove_nth/4 ← called preds: remove_nth 13694 times ---------------------------------------------------------------- *101/5050 user:lotto649/1 *4949/5050 user:make_intlist/3 74841 9999 *5050 user:make_intlist/3 *4949/5050 user:make_intlist/3 ---------------------------------------------------------------- *101/2027 user:lotto_loop/9 *1926/2027 user:writelist/1 18417 6474 *2027 user:writelist/1 *1926/2027 user:writelist/1 -- etc
Efficiency: determinism • Nondeterminacy: multiple solutions via backtracking • a major feature of Prolog: search • also a source of inefficiency when multiple solutions not needed • possible to get erroneous solns on backtracking as well • SicstusPrologindexes clauses based on first argument • a hash table created, with first arg value as hash key • interpreter will very quickly unify a call with its “hashed” clause; no need for unification on inappropriate calls. • But if a clause has a variable 1st arg, this won’t work • When possible, make predicates determinate • each clause takes care of one case, given in 1st arg • Result: • faster execution • less memory usage
Example: determinism COSC 2P93 Prolog: Lisp % make integer list... intlist(N, L) :- N > 0, make_intlist(0, N, L). make_intlist(M, N, [ ]) :- M > N. make_intlist(M, N, [M|L2]) :- M =< N, M2 is M+1, make_intlist(M2, N, L2). Notice how 1starg to make_intlist/3 is variable. So Prolog must use inefficient unification to match calls with clauses.
Determinism COSC 2P93 Prolog: Lisp intlist(N, L) :- N > 0, make_intlist(L, 0, N). make_intlist([ ], M, N) :- M > N. make_intlist([M|L2], M, N) :- M =< N, M2 is M+1, make_intlist(L2, M2, N). Here, we’ve moved 3rdarg (solution list) to 1st position. This version is faster: calls to make_intlist can be resolved instantly using 1st argument value.
Determinacy checker COSC 2P93 Prolog: Lisp command: spdetfile_name If we call it on “inefficient” make_intlist... * Non-determinate: user:make_intlist/3 (clause 1) * Indexing cannot distinguish this from clause 2. spdet –D (file) will also generate declarations to use... :- nondetuser:make_intlist/3.