-
Notifications
You must be signed in to change notification settings - Fork 650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Slow lia goal #10743
Comments
FTR, I don't see anything suspicious in the profiling on 110e87a. It is likely a genuinely hard problem, all the time is spent in CNF normalization of micromega. |
@ppedrot : the goal features subterms like |
I think it has to do with the exponential CNF phase here is a more contrived example
|
@amahboubi indeed, but it's still hard to get a reasonable heuristic that wouldn't endanger the results of the tactics. It might be the case that you use the exponentials in some opaque way. @thery I concur, but even if the algorithm is exponential there is room for improvement. With two trivial optimizations below (directly in OCaml but that should be backported to the Coq source), @samuelgruetter's example fails three times faster. diff --git a/plugins/micromega/micromega.ml b/plugins/micromega/micromega.ml
index cd620bd4a9..63d84544e7 100644
--- a/plugins/micromega/micromega.ml
+++ b/plugins/micromega/micromega.ml
@@ -1075,11 +1075,8 @@ let or_clause_cnf unsat deduce t0 f =
('a1 -> bool) -> ('a1 -> 'a1 -> 'a1 option) -> ('a1, 'a2) cnf -> ('a1,
'a2) cnf -> ('a1, 'a2) cnf **)
-let rec or_cnf unsat deduce f f' =
- match f with
- | [] -> cnf_tt
- | e::rst ->
- app (or_cnf unsat deduce rst f') (or_clause_cnf unsat deduce e f')
+let or_cnf unsat deduce f f' =
+ CList.map_append (fun e -> or_clause_cnf unsat deduce e f') f
(** val and_cnf : ('a1, 'a2) cnf -> ('a1, 'a2) cnf -> ('a1, 'a2) cnf **)
@@ -1162,7 +1159,7 @@ let ror_clause_cnf unsat deduce t0 f =
let acc,tg = pat in
(match ror_clause unsat deduce t0 e with
| Inl cl -> (cl::acc),tg
- | Inr l -> acc,(app tg l))) ([],[]) f
+ | Inr l -> acc,(l :: tg))) ([],[]) f
(** val ror_cnf :
('a1 -> bool) -> ('a1 -> 'a1 -> 'a1 option) -> ('a1 * 'a2) list list ->
@@ -1174,7 +1171,7 @@ let rec ror_cnf unsat deduce f f' =
| e::rst ->
let rst_f',t0 = ror_cnf unsat deduce rst f' in
let e_f',t' = ror_clause_cnf unsat deduce e f' in
- (app rst_f' e_f'),(app t0 t')
+ (app rst_f' e_f'),(app t0 (List.concat @@ List.rev t'))
(** val rxcnf :
('a2 -> bool) -> ('a2 -> 'a2 -> 'a2 option) -> ('a1 -> 'a3 -> ('a2, 'a3)
diff --git a/plugins/micromega/micromega.mli b/plugins/micromega/micromega.mli
index 6da0c754f4..bdd72ab19f 100644
--- a/plugins/micromega/micromega.mli
+++ b/plugins/micromega/micromega.mli
@@ -317,7 +317,7 @@ val ror_clause :
val ror_clause_cnf :
('a1 -> bool) -> ('a1 -> 'a1 -> 'a1 option) -> ('a1 * 'a2) list -> ('a1, 'a2) clause
- list -> ('a1, 'a2) clause list * 'a2 list
+ list -> ('a1, 'a2) clause list * 'a2 list list
val ror_cnf :
('a1 -> bool) -> ('a1 -> 'a1 -> 'a1 option) -> ('a1 * 'a2) list list -> ('a1, 'a2) The morale is that we shouldn't append lists one by one... |
On the other hand, it is quite easy in this case to see that the formula is false, e.g. with |
It is good to know that the CNF can be a bottleneck. Eventually, the solution is to not use a CNF... |
@fajb I am working on the Coq backport. Is it still the case that we generate |
Yes, micromega.ml is still extracted. |
The |
I guess this is to pass the test-suite which indeed extracts in a slightly different way -- and complains about the formatting. |
@samuelgruetter your example seems to contain lots of conditionals with the same condition. If you define
now on your example
fails almost immediately. Of course, this may work only if you have many expressions on the same modulo. I would be very interested to know if this little trick makes |
@thery very nice. |
apparently the benefit is not so big. I've been the victim of some cache effect of lia. |
I experienced some inconsistent timings/surprises too while experimenting with lia timings. Is there a way to turn off lia caching? |
@samuelgruetter , currently there is no way to turn it off. |
@samuelgruetter |
@samuelgruetter did you try the optimization on other examples? |
@thery I tried your optimization on some more examples here: mit-plv/bedrock2#98 |
@samuelgruetter very good! And hopefully with the new release of 8.10 you will get another significant speedup! |
Here's another example where I think Require Import Coq.Lists.List. Import ListNotations.
Require Import Coq.ZArith.ZArith.
Require Import Coq.micromega.Lia.
Axiom word: Type.
Axiom stmt: Type.
Axiom stackalloc_size: stmt -> Z.
Axiom bytes_per_word: Z.
Axiom list_union: forall {A: Type}, (A -> A -> bool) -> list A -> list A -> list A.
Axiom modVars_as_list: (Z -> Z -> bool) -> stmt -> list Z.
Module word.
Axiom of_Z: Z -> word.
End word.
Declare Scope word_scope.
Notation "! n" := (word.of_Z n) (at level 0, n at level 0, format "! n") : word_scope.
Notation "# n" := (Z.of_nat n) (at level 0, n at level 0, format "# n") : word_scope.
Delimit Scope word_scope with word.
Open Scope word_scope.
Open Scope Z_scope.
Goal forall (rem_framewords : Z) (program_base : word) (pos : Z) (pos0 : Z) (body: stmt)
(unused_scratch old_argvals old_retvals : list word) (old_ra : word)
(old_modvarvals ToSplit old_scratch : list word)
(argnames retnames: list Z),
0 <= rem_framewords ->
rem_framewords +
(stackalloc_size body / bytes_per_word +
#(length argnames + length retnames + 1 +
length (list_union Z.eqb (modVars_as_list Z.eqb body) argnames))) <=
#(length
((((((ToSplit ++ old_scratch) ++ old_modvarvals) ++ [old_ra]) ++ old_retvals) ++ old_argvals) ++
unused_scratch)) ->
bytes_per_word = 4 \/ bytes_per_word = 8 ->
0 <= stackalloc_size body / bytes_per_word ->
length
(((((ToSplit ++ old_scratch) ++ old_modvarvals) ++ [old_ra]) ++ old_retvals) ++ old_argvals) =
(length
((((((ToSplit ++ old_scratch) ++ old_modvarvals) ++ [old_ra]) ++ old_retvals) ++ old_argvals) ++
unused_scratch) - Z.to_nat rem_framewords)%nat ->
length unused_scratch = Z.to_nat rem_framewords ->
length ((((ToSplit ++ old_scratch) ++ old_modvarvals) ++ [old_ra]) ++ old_retvals) =
(length
(((((ToSplit ++ old_scratch) ++ old_modvarvals) ++ [old_ra]) ++ old_retvals) ++ old_argvals) -
length argnames)%nat ->
length old_argvals = length argnames ->
length (((ToSplit ++ old_scratch) ++ old_modvarvals) ++ [old_ra]) =
(length ((((ToSplit ++ old_scratch) ++ old_modvarvals) ++ [old_ra]) ++ old_retvals) -
length retnames)%nat ->
length old_retvals = length retnames ->
length ((ToSplit ++ old_scratch) ++ old_modvarvals) =
(length (((ToSplit ++ old_scratch) ++ old_modvarvals) ++ [old_ra]) - 1)%nat ->
length [old_ra] = 1%nat ->
length (ToSplit ++ old_scratch) =
(length ((ToSplit ++ old_scratch) ++ old_modvarvals) -
length (list_union Z.eqb (modVars_as_list Z.eqb body) argnames))%nat ->
length old_modvarvals = length (list_union Z.eqb (modVars_as_list Z.eqb body) argnames) ->
length ToSplit =
(length (ToSplit ++ old_scratch) - Z.to_nat (stackalloc_size body / bytes_per_word))%nat ->
length old_scratch = Z.to_nat (stackalloc_size body / bytes_per_word) ->
length old_scratch = Z.to_nat (stackalloc_size body / bytes_per_word) /\
length old_modvarvals = length (list_union Z.eqb (modVars_as_list Z.eqb body) argnames) /\
length old_retvals = length retnames /\
length old_argvals = length argnames /\
length unused_scratch = Z.to_nat rem_framewords.
Proof.
intros.
(* zify. (* creates many disjunctions of the form "0 < z /\ z0 = z \/ z <= 0 /\ z0 = 0" *) *)
(* Time repeat split; assumption. (* 0 secs *) *)
Time lia. (* 19 secs *)
Qed. Coq version: 8.11.0 Contrary to the original example in this issue, this goal came up without using I didn't make a new issue for this because it seems to be the same underlying problem, and based on @fajb's comment here, I'm not expecting any improvement anytime soon, just wanted to record this test case here. On the bright side: I'm adding a feature to a compiler whose correctness proof I wrote in Coq 8.9, and now I'm working in 8.11, and there are several goals where in 8.9, I had to manually clear all hypotheses except a few useful ones to make lia fast enough, and now I can often just keep all hypotheses without worrying about which ones are used, and lia still is fast enough, so even if Coq doesn't provide the kind of arithmetic solver I'd like yet, some noticeable improvement has happened 😃 |
👍 |
To add to the collection of test cases, here's a case where Require Import ZArith Lia. Open Scope Z_scope.
Goal forall i w0 wi bpw : Z,
0 <= w0 < 2 ^ wi ->
(w0 + (bpw mod 2 ^ wi * Z.of_nat (Z.to_nat i)) mod 2 ^ wi) mod 2 ^ wi =
(w0 + (i * bpw) mod 2 ^ wi) mod 2 ^ wi.
Proof.
intros. Z.div_mod_to_equations.
Fail lia. Using @fajb's itauto, Coq version: master (77d56f3) |
It might make sense to add @thery 's trick to |
Here's a goal on which
lia
(andomega
as well) is very slow:Note that
lia
can't solve this, and that's fine, but I would likelia
to fail faster, so that I can write proof scripts which try many things.In Coq 8.9.0, this takes 80s, and in Coq master, this takes 15s using
coqc
from the command line, and interestingly, it takes 40s-50s in Proof General./cc @andres-erbsen @fajb
The text was updated successfully, but these errors were encountered: