Zulip Chat Archive
Stream: mathlib4
Topic: weird performance of norm_num with Nat.digits
David Renshaw (Sep 29 2023 at 18:35):
I'm observing norm_num
to be surprisingly slow (taking almost a full second) on Nat.digits 10 N
when N
is between ~500 and ~1500.
On larger values of N
, norm_num
finishes much more quickly (like 40ms).
Does anyone have any idea about what might explain this behavior?
import Mathlib.Data.Nat.Digits
set_option trace.profiler true
example : [0,0,7] = Nat.digits 10 700 := by norm_num
-- [Elab.command] [0.769513s] example : [0, 0, 7] = Nat.digits 10 700 := by norm_num
example : [0,0,8] = Nat.digits 10 800 := by norm_num
-- [Elab.command] [0.807374s] example : [0, 0, 8] = Nat.digits 10 800 := by norm_num
example : [0,0,9] = Nat.digits 10 900 := by norm_num
-- [Elab.command] [0.765223s] example : [0, 0, 9] = Nat.digits 10 900 := by norm_num
example : [0,0,0,1] = Nat.digits 10 1000 := by norm_num
-- [Elab.command] [0.741816s] example : [0, 0, 0, 1] = Nat.digits 10 1000 := by norm_num
-- The folowing examples are much faster! Why is that?
example : [0,0,8,1] = Nat.digits 10 1800 := by norm_num
-- [Elab.command] [0.033864s] example : [0, 0, 8, 1] = Nat.digits 10 1800 := by norm_num
example : [0,0,8,2] = Nat.digits 10 2800 := by norm_num
-- [Elab.command] [0.032935s] example : [0, 0, 8, 2] = Nat.digits 10 2800 := by norm_num
example : [9,9,9,9,9] = Nat.digits 10 99999 := by norm_num
-- [Elab.command] [0.035004s] example : [9, 9, 9, 9, 9] = Nat.digits 10 99999 := by norm_num
example : [0,0,0,0,0,0,0,1] = Nat.digits 10 10000000 := by norm_num
-- [Elab.command] [0.043711s] example : [0, 0, 0, 0, 0, 0, 0, 1] = Nat.digits 10 10000000 := by norm_num
Kyle Miller (Sep 29 2023 at 18:37):
norm_num
uses simp
-- maybe for small enough numbers it leans more on simp
? How does norm_num1
do?
David Renshaw (Sep 29 2023 at 18:38):
norm_num1
is unable to close any of these goals
David Renshaw (Sep 29 2023 at 18:39):
simp
can close all but the bottom three of them
Kevin Buzzard (Sep 29 2023 at 18:50):
Similar weirdness here, with 317 being some kind of hole where one algorithm stopped being used and another one took over (and as it turned out didn't work).
Kyle Miller (Sep 29 2023 at 18:56):
I guess norm_num1
doesn't work because there's no norm num extension for Nat.digits
. The way it's working is that it repeatedly applies Nat.digits_add_two_add_one
using simp
and then reduces the numbers involved.
Kyle Miller (Sep 29 2023 at 18:59):
I think this is what the first one ends up doing, minus some intermediate simplifications using List.cons.injEq
:
example : [0,0,7] = Nat.digits 10 700 := by
rw [Nat.digits_add_two_add_one]
norm_num1
rw [Nat.digits_add_two_add_one]
norm_num1
rw [Nat.digits_add_two_add_one]
norm_num1
rfl
David Renshaw (Sep 29 2023 at 19:01):
that proof is fast
David Renshaw (Sep 29 2023 at 19:01):
so I guess maybe simp
gets distracted with some other irrelevant stuff
David Renshaw (Sep 29 2023 at 19:02):
The conclusion that I'm drawing is that we should port the Nat.digits norm_num extension before worrying too much about this
David Renshaw (Sep 29 2023 at 19:02):
though it would still be nice to understand exactly why norm_num
/ simp
is currently so slow here
David Renshaw (Sep 29 2023 at 19:13):
hm... but it looks like norm_num
extensions in mathlib4 must return an integer or rational, and Nat.digits
does not fit into that model?
This plugin is noticeably less powerful than the equivalent version in Mathlib 3: the design of
norm_num
means plugins have to return numerals, rather than a generic expression.
Kyle Miller (Sep 29 2023 at 19:31):
Yeah, I've been wondering about that. I'm not sure what the plan is for normalizing things like lists, vectors, and matrices is.
David Renshaw (Sep 29 2023 at 20:34):
This works for the above digits
examples, and speeds up the small examples (but slows down the large examples):
repeat (simp (config := {decide := false} ) <;> norm_num1)
Alex J. Best (Sep 30 2023 at 15:49):
Funily enough I started making a PR to change that simp lemma yesterday #7428 I don't think its a particularly good one as it breaks numerals down which you almost never want.
David Renshaw (Sep 30 2023 at 17:19):
yeah, I saw that. I tried it on the above examples and it did not seem to make much difference.
Alex J. Best (Sep 30 2023 at 21:58):
Ah thats sad to hear. I still think its a better simp lemma though. I guess it makes sense that there is a deeper root cause though, norm_num
probably doesn't have issues adding the numbers back together again when needed.
David Renshaw (Oct 02 2023 at 17:14):
I built lean configured with RELWITHDEBINFO
and ran it on the slow example, yielding this flame graph. (15MB, will take a few moments to load in your browser).
David Renshaw (Oct 02 2023 at 17:15):
a very substantial proportion of the time is being spent in whnf stuff
David Renshaw (Oct 02 2023 at 17:19):
29.74% of the time being spent in l_Lean_Meta_whnfEasyCases___at_Lean_Meta_whnfCore_go___spec__3
David Renshaw (Oct 02 2023 at 17:23):
looks like approximately 85% of the time is spent in functions with names containing "whnf", "reduceMatcher", "forallBoundedTelescope", or "forallTelescopeReducing".
David Renshaw (Oct 22 2023 at 18:29):
Here is maybe a more striking illustration:
import Mathlib.Data.Nat.Digits
set_option maxHeartbeats 5000 in
example : [0,0,0,1] = Nat.digits 10 1000 := by norm_num
-- tactic 'simp' failed, nested error:
-- (deterministic) timeout at 'simp', maximum number of heartbeats (5000) has been reached (use 'set_option maxHeartbeats <num>' to set the limit)
set_option maxHeartbeats 1000 in
example : [0,0,8,1] = Nat.digits 10 1800 := by norm_num
-- goals accomplished
David Renshaw (Oct 22 2023 at 18:30):
I wonder whether https://github.com/leanprover/lean4/commit/9a7565d66c84bdef37f8cd021fe6ca6eebe7b425 fixes this...
Mauricio Collares (Oct 22 2023 at 18:47):
Works in #7834, so probably norm_num
doesn't expect simp
to use decide
.
David Renshaw (Oct 22 2023 at 18:52):
interesting!
David Renshaw (Oct 22 2023 at 18:57):
look to me like lean4#2730 does not fix this problem.
David Renshaw (Oct 22 2023 at 19:27):
I can test out turning off decide
in norm_num
like this:
diff --git a/Mathlib/Tactic/NormNum/Core.lean b/Mathlib/Tactic/NormNum/Core.lean
index ddb35ca06..f82790298 100644
--- a/Mathlib/Tactic/NormNum/Core.lean
+++ b/Mathlib/Tactic/NormNum/Core.lean
@@ -233,6 +233,7 @@ def normNumAt (g : MVarId) (ctx : Simp.Context) (fvarIdsToSimp : Array FVarId)
let mut g := g
let mut toAssert := #[]
let mut replaced := #[]
+ let ctx := { ctx with config := {ctx.config with decide := false}}
for fvarId in fvarIdsToSimp do
let localDecl ← fvarId.getDecl
let type ← instantiateMVars localDecl.type
Indeed, that makes the maxHeartbeats 5000
example pass, and it makes the slow examples from further above somewhat faster. But they are still slower than I would expect.
Last updated: Dec 20 2023 at 11:08 UTC