mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-21 00:39:37 +00:00
Estimated hours taken: 10
Branches: main
Fix several performance problems that arose when compiling predicates defined
by lots of facts, e.g. 16000 facts of the edge relation used in my recent
paper with Kostis on tabling. We couldn't use fact tables because they return
solutions in a different order, which would be OK semantically but which
invalidated the performance comparison we tried to make. I had to either fix
fact tables to use standard order or fix the performance problems of the usual
path of compilation. The latter is more generally useful, and reduces the
chances of any similar surprises.
The first performance problem was the quadratic complexity of always adding
clauses at the end of the clause list. The fix is to keep the list in reverse
order while it is being added to. This takes the time to read in the 16000-fact
predicate from 80+s to ~1s.
The second performance problem was our use of generate/test to check whether
any clause had impure code when copying the clauses to procs. Since we don't
have tail recursion for model_non code, this couldn't reuse stack frames, and
as a result ran at memory speed, not cache speed. The fix is to use an explicit
recursive predicate. (A better fix would be to implement proper tail recursion
for model_non code, but that would take significantly longer.) This fix
doesn't make much difference in the usual grades, but makes a significant
difference in debug grades.
The third performance problem is the quadratic or worse behavior of the mode
checker when merging the instmaps of disjuncts at the ends of large
disjunctions. With our old setup, this was inevitable, since given a bunch of
facts of the form
edge(1, 2).
edge(2, 3).
edge(3, 4).
...
etc, the instmap delta the mode checker builds for HeadVar__1 for the
disjunction of clauses is free -> bound(1; 2; 3; ...). The solution to that
is to add a new pragma, mode_check_clauses, that causes the mode checker
to check each clause individually, and to create a simple free -> ground
(or whatever the declared mode calls for) instmap delta for HeadVar__1.
We should consider making this pragma be implied for predicates defined
by lots of facts, but this diff does not do that. This change takes the
time to modecheck that 16000-fact predicate from ~360s to ~5s.
Theoretically, another way of tackling the problem would have been to
introduce widening, in which any list of functors inside "bound()" whose
length was above the threshold would be replaced by "ground", if the arguments
of the functors themselves were bound. However, that change would be much more
complex, and its behavior would be hard for users to predict. In a future
constraint-based mode system with separate passes for computing
producers/consumers and for computing the set of function symbols a
variable can be bound to, we could simply disable the latter pass
for predicates like this.
There are at least three other performance problems left. The fourth is
the same as the third, except for unique mode checking. The fifth is in
simplification, and the sixth is in equiv_type_hlds. I'll work on those next.
compiler/hlds_pred.m:
Instead of exposing the clause list part of the clause_info, make it
an abstract type. Make the abstract type keep the reverse list when
adding clauses to the list, the forward list when the later parts
of the compiler manipulate the clause list, and both when it is
convenient to do so. Add the predicates necessary to manipulate
this more complex representation. Move the code manipulating
clauses_infos into its own implementation section, next to its
declarations.
Add a new mode_check_clauses predicate marker, which records the
presence of the new pragma.
Fix departures from our coding standard.
compiler/clause_to_proc.m:
Replace the generate/test code with an explicit loop, as
mentioned above.
compiler/prog_data.m:
Add the new pragma.
compiler/prog_io_pragma.m:
Recognize the new pragma.
compiler/modes.m:
Implement separate mode checking of clauses.
Convert the file to use four-space indentation to reduce the number of
bad line breaks. Give some predicates more meaningful names.
Use error_util to print some error messages. Fix departures from
our coding standard.
compiler/make_hlds.m:
compiler/hlds_out.m:
compiler/intermod.m:
compiler/mercury_to_mercury.m:
compiler/module_qual.m:
compiler/modules.m:
compiler/recompilation.version.m:
Handle the new pragma.
compiler/*.m:
Conform to the change in hlds_pred.m and/or prog_data.m.
In some cases, convert files to four-space indentation.
tests/hard_coded/mode_check_clauses.{m,exp}:
A new test case to check that the mode_check_clauses pragma works
correctly.
tests/hard_coded/Mmakefile:
Enable the new test case.
205 lines
7.3 KiB
Mathematica
205 lines
7.3 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 1997-2005 University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% This module looks after goal paths, which associate each goal
|
|
% with its position in a procedure definition,
|
|
|
|
% Main author: zs.
|
|
|
|
:- module check_hlds__goal_path.
|
|
|
|
:- interface.
|
|
|
|
:- import_module hlds__hlds_goal.
|
|
:- import_module hlds__hlds_module.
|
|
:- import_module hlds__hlds_pred.
|
|
|
|
:- import_module bool.
|
|
|
|
% IMPORTANT: the type constraint_id in hlds_data.m makes use of
|
|
% goal_paths to identify constraints between the typechecking pass
|
|
% and the polymorphism pass. For this reason, goal paths should not
|
|
% be recalculated anywhere between these two passes. See the XXX
|
|
% comment near the declaration of constraint_id.
|
|
|
|
:- pred goal_path__fill_slots(module_info::in, proc_info::in, proc_info::out)
|
|
is det.
|
|
|
|
% Fill in the goal_paths for goals in the clauses_info of the predicate.
|
|
% Clauses are given goal paths `disj(1)', ..., `disj(N)'. If the bool
|
|
% argument is true then the goal paths are stored in a form where any
|
|
% prefix consisting of `disj(_)', `neg', `exist(_)' and `ite_else'
|
|
% components is removed. This is used to optimise the constraint-based
|
|
% mode analysis where the instantiatedness of a variable at such a goal
|
|
% path is always equivalent to its instantiatedness at the parent goal
|
|
% path.
|
|
|
|
:- pred goal_path__fill_slots_in_clauses(module_info::in, bool::in,
|
|
pred_info::in, pred_info::out) is det.
|
|
|
|
:- pred goal_path__fill_slots_in_goal(hlds_goal::in, vartypes::in,
|
|
module_info::in, hlds_goal::out) is det.
|
|
|
|
:- implementation.
|
|
|
|
:- import_module check_hlds__type_util.
|
|
:- import_module hlds__hlds_data.
|
|
:- import_module hlds__hlds_goal.
|
|
:- import_module parse_tree__prog_data.
|
|
|
|
:- import_module char.
|
|
:- import_module int.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module require.
|
|
:- import_module std_util.
|
|
|
|
:- type slot_info
|
|
---> slot_info(
|
|
vartypes :: vartypes,
|
|
module_info :: module_info,
|
|
omit_mode_equiv_prefix :: bool
|
|
).
|
|
|
|
goal_path__fill_slots(ModuleInfo, !Proc) :-
|
|
proc_info_goal(!.Proc, Goal0),
|
|
proc_info_vartypes(!.Proc, VarTypes),
|
|
goal_path__fill_slots_in_goal(Goal0, VarTypes, ModuleInfo, Goal),
|
|
proc_info_set_goal(Goal, !Proc).
|
|
|
|
goal_path__fill_slots_in_clauses(ModuleInfo, OmitModeEquivPrefix, !PredInfo) :-
|
|
pred_info_clauses_info(!.PredInfo, ClausesInfo0),
|
|
clauses_info_clauses_only(ClausesInfo0, Clauses0),
|
|
clauses_info_vartypes(ClausesInfo0, VarTypes),
|
|
SlotInfo = slot_info(VarTypes, ModuleInfo, OmitModeEquivPrefix),
|
|
list__map_foldl(fill_slots_in_clause(SlotInfo), Clauses0, Clauses,
|
|
1, _),
|
|
clauses_info_set_clauses(Clauses, ClausesInfo0, ClausesInfo),
|
|
pred_info_set_clauses_info(ClausesInfo, !PredInfo).
|
|
|
|
:- pred fill_slots_in_clause(slot_info::in, clause::in, clause::out,
|
|
int::in, int::out) is det.
|
|
|
|
fill_slots_in_clause(SlotInfo, Clause0, Clause, ClauseNum, ClauseNum + 1) :-
|
|
Clause0 = clause(ProcIds, Goal0, Lang, Context),
|
|
fill_goal_slots(Goal0, [disj(ClauseNum)], SlotInfo, Goal),
|
|
Clause = clause(ProcIds, Goal, Lang, Context).
|
|
|
|
goal_path__fill_slots_in_goal(Goal0, VarTypes, ModuleInfo, Goal) :-
|
|
SlotInfo = slot_info(VarTypes, ModuleInfo, no),
|
|
fill_goal_slots(Goal0, [], SlotInfo, Goal).
|
|
|
|
:- pred fill_goal_slots(hlds_goal::in, goal_path::in, slot_info::in,
|
|
hlds_goal::out) is det.
|
|
|
|
fill_goal_slots(Expr0 - Info0, Path0, SlotInfo, Expr - Info) :-
|
|
OmitModeEquivPrefix = SlotInfo ^ omit_mode_equiv_prefix,
|
|
(
|
|
OmitModeEquivPrefix = yes,
|
|
list__takewhile(mode_equiv_step, Path0, _, Path)
|
|
;
|
|
OmitModeEquivPrefix = no,
|
|
Path = Path0
|
|
),
|
|
goal_info_set_goal_path(Info0, Path, Info),
|
|
fill_expr_slots(Expr0, Info, Path0, SlotInfo, Expr).
|
|
|
|
:- pred mode_equiv_step(goal_path_step::in) is semidet.
|
|
|
|
mode_equiv_step(Step) :-
|
|
( Step = disj(_)
|
|
; Step = neg
|
|
; Step = scope(_)
|
|
; Step = ite_else
|
|
).
|
|
|
|
:- pred fill_expr_slots(hlds_goal_expr::in, hlds_goal_info::in, goal_path::in,
|
|
slot_info::in, hlds_goal_expr::out) is det.
|
|
|
|
fill_expr_slots(conj(Goals0), _, Path0, SlotInfo, conj(Goals)) :-
|
|
fill_conj_slots(Goals0, Path0, 0, SlotInfo, Goals).
|
|
fill_expr_slots(par_conj(Goals0), _, Path0, SlotInfo,
|
|
par_conj(Goals)) :-
|
|
fill_conj_slots(Goals0, Path0, 0, SlotInfo, Goals).
|
|
fill_expr_slots(disj(Goals0), _, Path0, SlotInfo, disj(Goals)) :-
|
|
fill_disj_slots(Goals0, Path0, 0, SlotInfo, Goals).
|
|
fill_expr_slots(switch(Var, B, Cases0), _, Path0, SlotInfo,
|
|
switch(Var, B, Cases)) :-
|
|
VarTypes = SlotInfo ^ vartypes,
|
|
ModuleInfo = SlotInfo ^ module_info,
|
|
map__lookup(VarTypes, Var, Type),
|
|
(
|
|
type_util__switch_type_num_functors(ModuleInfo, Type,
|
|
NumFunctors)
|
|
->
|
|
NumCases = NumFunctors
|
|
;
|
|
NumCases = -1
|
|
),
|
|
fill_switch_slots(Cases0, Path0, 0, NumCases, SlotInfo, Cases).
|
|
fill_expr_slots(not(Goal0), _, Path0, SlotInfo, not(Goal)) :-
|
|
fill_goal_slots(Goal0, [neg | Path0], SlotInfo, Goal).
|
|
fill_expr_slots(scope(Reason, Goal0), OuterInfo, Path0, SlotInfo,
|
|
scope(Reason, Goal)) :-
|
|
Goal0 = _ - InnerInfo,
|
|
goal_info_get_determinism(OuterInfo, OuterDetism),
|
|
goal_info_get_determinism(InnerInfo, InnerDetism),
|
|
( InnerDetism = OuterDetism ->
|
|
MaybeCut = no_cut
|
|
;
|
|
MaybeCut = cut
|
|
),
|
|
fill_goal_slots(Goal0, [scope(MaybeCut) | Path0], SlotInfo, Goal).
|
|
fill_expr_slots(if_then_else(A, Cond0, Then0, Else0), _, Path0, SlotInfo,
|
|
if_then_else(A, Cond, Then, Else)) :-
|
|
fill_goal_slots(Cond0, [ite_cond | Path0], SlotInfo, Cond),
|
|
fill_goal_slots(Then0, [ite_then | Path0], SlotInfo, Then),
|
|
fill_goal_slots(Else0, [ite_else | Path0], SlotInfo, Else).
|
|
fill_expr_slots(unify(LHS, RHS0, Mode, Kind, Context), _, Path0, SlotInfo,
|
|
unify(LHS, RHS, Mode, Kind, Context)) :-
|
|
(
|
|
RHS0 = lambda_goal(A, B, C, D, E, F, G, H, LambdaGoal0)
|
|
->
|
|
fill_goal_slots(LambdaGoal0, Path0, SlotInfo, LambdaGoal),
|
|
RHS = lambda_goal(A, B, C, D, E, F, G, H, LambdaGoal)
|
|
;
|
|
RHS = RHS0
|
|
).
|
|
fill_expr_slots(Goal @ call(_, _, _, _, _, _), _, _, _, Goal).
|
|
fill_expr_slots(Goal @ generic_call(_, _, _, _), _, _, _, Goal).
|
|
fill_expr_slots(Goal @ foreign_proc(_, _, _, _, _, _), _, _, _, Goal).
|
|
fill_expr_slots(shorthand(_), _, _, _, _) :-
|
|
% these should have been expanded out by now
|
|
error("fill_expr_slots: unexpected shorthand").
|
|
|
|
:- pred fill_conj_slots(list(hlds_goal)::in, goal_path::in, int::in,
|
|
slot_info::in, list(hlds_goal)::out) is det.
|
|
|
|
fill_conj_slots([], _, _, _, []).
|
|
fill_conj_slots([Goal0 | Goals0], Path0, N0, SlotInfo, [Goal | Goals]) :-
|
|
N1 = N0 + 1,
|
|
fill_goal_slots(Goal0, [conj(N1) | Path0], SlotInfo, Goal),
|
|
fill_conj_slots(Goals0, Path0, N1, SlotInfo, Goals).
|
|
|
|
:- pred fill_disj_slots(list(hlds_goal)::in, goal_path::in, int::in,
|
|
slot_info::in, list(hlds_goal)::out) is det.
|
|
|
|
fill_disj_slots([], _, _, _, []).
|
|
fill_disj_slots([Goal0 | Goals0], Path0, N0, SlotInfo, [Goal | Goals]) :-
|
|
N1 = N0 + 1,
|
|
fill_goal_slots(Goal0, [disj(N1) | Path0], SlotInfo, Goal),
|
|
fill_disj_slots(Goals0, Path0, N1, SlotInfo, Goals).
|
|
|
|
:- pred fill_switch_slots(list(case)::in, goal_path::in, int::in, int::in,
|
|
slot_info::in, list(case)::out) is det.
|
|
|
|
fill_switch_slots([], _, _, _, _, []).
|
|
fill_switch_slots([case(A, Goal0) | Cases0], Path0, N0, NumCases, SlotInfo,
|
|
[case(A, Goal) | Cases]) :-
|
|
N1 = N0 + 1,
|
|
fill_goal_slots(Goal0, [switch(N1, NumCases) | Path0], SlotInfo, Goal),
|
|
fill_switch_slots(Cases0, Path0, N1, NumCases, SlotInfo, Cases).
|