Zulip Chat Archive

Stream: mathlib4

Topic: simp_all out of control


Kevin Buzzard (Oct 29 2023 at 12:35):

I was experimenting with how far one could simp NNG away (starting at 1 just for fun). The last simp_all takes over 4 seconds on my machine with mathlib imported, and 0.2 seconds without it. I know it's not an appropriate goal for simp_all but when I look at the logs with mathlib imported they are really wild, talking about advanced stuff which is absolutely nothing to do with anything. Is this expected? Have I just committed too much simp abuse for some reason? Sample trace output then mathlib code:

...
          [Meta.Tactic.simp.unify] @HilbertBasis.coe_mk:1000, failed to unify
                fun i => ↑(LinearIsometryEquiv.symm (HilbertBasis.mk ?hv ?hsp).repr) (lp.single 2 i 1)
              with
                k * b = c * k → b = c
          [Meta.Tactic.simp.unify] @HilbertBasis.coe_mkOfOrthogonalEqBot:1000, failed to unify
                fun i => ↑(LinearIsometryEquiv.symm (HilbertBasis.mkOfOrthogonalEqBot ?hv ?hsp).repr) (lp.single 2 i 1)
              with
                k * b = c * k → b = c
          [Meta.Tactic.simp.unify] @OrthonormalBasis.coe_toHilbertBasis:1000, failed to unify
                fun i => ↑(LinearIsometryEquiv.symm (OrthonormalBasis.toHilbertBasis ?b).repr) (lp.single 2 i 1)
              with
                k * b = c * k → b = c
          [Meta.Tactic.simp.unify] @coe_fourierBasis:1000, failed to unify
                fun i => ↑(LinearIsometryEquiv.symm fourierBasis.repr) (lp.single 2 i 1)
              with
                k * b = c * k → b = c
          [Meta.Tactic.simp.unify] @Combinatorics.Line.map_apply:1000, failed to unify
                (fun x i => Option.getD (Combinatorics.Line.idxFun (Combinatorics.Line.map ?f ?l) i) x) (?f ?x)
              with
                k * b = c * k → b = c
          [Meta.Tactic.simp.unify] @Combinatorics.Line.vertical_apply:1000, failed to unify
                (fun x i => Option.getD (Combinatorics.Line.idxFun (Combinatorics.Line.vertical ?v ?l) i) x) ?x
              with
                k * b = c * k → b = c
...
import Mathlib -- if you dare

inductive MyNat
| one : MyNat
| S : MyNat  MyNat

notation (name := MyNatNotation) (priority := 1000000) "ℕ" => MyNat

namespace MyNat

instance ofNat : OfNat MyNat 1 where
  ofNat := one

-- test
theorem one_eq_1 : one = 1 := rfl

-- use it for `one`
@[simp] theorem one_def : one = 1 := rfl

def add : MyNat  MyNat  MyNat
  | a, one  => S a
  | a, S b => S (add a b)

instance instAdd : Add MyNat where
  add := MyNat.add

@[simp]
theorem add_one (a : MyNat) : a + 1 = S a := by rfl

@[simp]
theorem add_S (a b : MyNat) : a + (S b) = S (a + b) := by rfl

theorem add_assoc (a b c : MyNat) : (a + b) + c = a + (b + c) := by
  induction c with
  | one => simp
  | S _ h => simp [h]

@[simp]
theorem one_add (a : ) : 1 + a = S a := by
  induction a with
  | one => simp
  | S _ h => simp [h]

@[simp]
theorem S_add (a b : ) : S a + b = S (a + b) := by
  induction b with
  | one => simp
  | S _ h => simp [h]

@[simp]
theorem add_comm (a b : ) : a + b = b + a := by
  induction b with
  | one => simp
  | S _ h => simp [h]

@[simp]
theorem add_left_comm (a b c : ) : a + b + c = a + c + b := by
  rw [add_assoc, add_comm b, add_assoc]

namespace MyNat

def mul : MyNat  MyNat  MyNat
  | a, 1   => a
  | a, b + 1 => (mul a b) + a

instance : Mul MyNat where
  mul := MyNat.mul

@[simp]
theorem mul_one (a : MyNat) : a * 1 = a := by rfl

@[simp] -- I only want it simp for now
theorem mul_S (a b : MyNat) : a * (S b) = a * b + a := by rfl

@[simp]
theorem one_mul (a : ) : 1 * a = a := by
  induction a with
  | one => simp [mul_one]
  | S _ h => simp [mul_S, h]

@[simp]
theorem S_mul (a b : ) : S a * b = a * b + b := by
  induction b with
  | one =>
    simp
  | S k h =>
    simp [h, mul_S,  add_assoc]

@[simp]
theorem mul_comm (a b : ) : a * b = b * a := by
  induction b with
  | one =>
    simp
  | S k hk =>
    simp [mul_S, hk]

set_option trace.profiler true in
--set_option trace.Meta.Tactic.simp true in
example (c k : ) (hk:  (b : ), k * b = k * c  b = c) (b: ) (h: S k * b = S k * c) :
  b = c := by
  simp_all
  sorry

end MyNat

Mauricio Collares (Oct 29 2023 at 13:20):

I think there are many lemmas/typeclass searches contributing here:

  • Mathlib/Algebra/GroupWithZero/Basic.lean has six simp lemmas in section CancelMonoidWithZero (assuming [CancelMonoidWithZero M₀]), and they all slow things down a bit. It feels as if some cache is being reset often.
  • Of the other half, the four simp lemmas in Mathlib/Algebra/Group/Basic.lean which take LeftCancelMonoid or RightCancelMonoid instances seem to be responsible for a lot of the search time, in the same manner as described above (each of the four lemmas cause a fourth of the total slowdown attributable to this file).

Kevin Buzzard (Oct 29 2023 at 13:26):

Oh nice detective work!

Mauricio Collares (Oct 29 2023 at 13:28):

Updated the first bullet point (I had previously claimed a NeZero instance was causing slowdowns, which wasn't the case).

Kevin Buzzard (Oct 29 2023 at 13:31):

Ironically, zero is the one thing I don't have in this file :-)

Kevin Buzzard (Oct 29 2023 at 15:46):

So this is probably a mathlib issue?

Mauricio Collares (Oct 29 2023 at 16:29):

I'm way out of my depth here, but I'd say this is a Lean issue. Mathlib adds many simp lemmas whose conclusions would be useful in your example, but Lean spends an unreasonable amount of time figuring out that the required instances aren't available. the Meta.synthInstance trace reports a lot of cache hits but there must be some expensive uncached steps (probably unrelated to typeclass inference). Let me try to record a perf trace.

Mauricio Collares (Oct 29 2023 at 17:48):

I inadvertently profiled with a debug build (which is slower for everything) and got a big surprise: https://gist.github.com/collares/a5cad26f73072c09694250ba6a21d5a0 -- you can simulate this with set_option trace.profiler true and set_option trace.profiler.threshold 1.

Mauricio Collares (Oct 29 2023 at 17:48):

Look at how many essentially-equal isDefEq checks! I know there probably is no caching due to the metavariables, but it's still a very surprising number of calls.

Mauricio Collares (Oct 29 2023 at 17:55):

In total, there are 2091 very similar isDefEq checks. ?a * ?b = ?a =?= k * (k * b) = c * k and ?a * ?b = ?b =?= k * (k * b) = c * k are the leaders with 104 times each.

Mauricio Collares (Oct 29 2023 at 18:22):

Probably best to use set_option pp.all true to see stuff like

        [Meta.isDefEq] [0.001818s]  @Eq.{?u.10505 + 1} ?M₀
              (@HMul.hMul.{?u.10505, ?u.10505, ?u.10505} ?M₀ ?M₀ ?M₀
                (@instHMul.{?u.10505} ?M₀
                  (@MulZeroClass.toMul.{?u.10505} ?M₀
                    (@MulZeroOneClass.toMulZeroClass.{?u.10505} ?M₀
                      (@MonoidWithZero.toMulZeroOneClass.{?u.10505} ?M₀
                        (@CancelMonoidWithZero.toMonoidWithZero.{?u.10505} ?M₀ ?inst)))))
                ?a ?b)
              ?a =?= @Eq.{1} MyNat
              (@HMul.hMul.{0, 0, 0} MyNat MyNat MyNat (@instHMul.{0} MyNat MyNat.MyNat.instMulMyNat) k b)
              (@HMul.hMul.{0, 0, 0} MyNat MyNat MyNat (@instHMul.{0} MyNat MyNat.MyNat.instMulMyNat) c k)

Last updated: Dec 20 2023 at 11:08 UTC