Zulip Chat Archive

Stream: lean4

Topic: Type checking time correlates with value in expression


Daniil Kisel (Nov 19 2023 at 22:10):

Type checking this code takes 4.6s and ~1GB mem on my pc:

def f : Bool :=
  let x := (480 : UInt32) |>.toUInt8
  if h: x < 24
    then
      have hh : x.toNat < 24 := h
      true
    else
      false

How can I make it faster and less memory consuming? I don't understand why the value (480) affects type checking time (bigger ~ slower). On 900 it takes 23.3s and 4GB.

Alex J. Best (Nov 19 2023 at 22:23):

The issue appears to be the have hh line if you comment that the tc is way faster.
Likely this ends up asking the kernel to do too much unfolding, or compute something in an inefficient way by saying that the type of h is defeq to that of hh.
If you replace things with a proof that makes use of tactics rather than defeq, e.g.

by  simpa [x] using h

it works way faster for me

Daniil Kisel (Nov 19 2023 at 22:34):

If I replace h with by simpa [x] using h it outputs error
"invalid 'simp', proposition expected
UInt8"..

Alex J. Best (Nov 19 2023 at 22:35):

Oh right the simpa tactic is in Std I guess.
You can use

 by
        simp [x] at h 
        exact h

for a pure lean equivalent

Daniil Kisel (Nov 19 2023 at 22:36):

I did import Std though

Kyle Miller (Nov 19 2023 at 22:36):

You're probably using an older version of Lean 4. It was the last couple months where you could do simp [x] with x a local definition.

Alex J. Best (Nov 19 2023 at 22:37):

import Std
def f : Bool :=
  let x := (900000 : UInt32) |>.toUInt8
  if h: x < 24
    then
      have hh : x.toNat < 24 := by simpa [x] using h
      true
    else
      false

works in the web editor, so I think Kyle must be right

Daniil Kisel (Nov 19 2023 at 22:37):

I'm on v4.2.0, I'll try with 4.3.0-rc2 now

Alex J. Best (Nov 19 2023 at 22:38):

If you are on an older version then maybe you can just get rid of the [x] even

Kyle Miller (Nov 19 2023 at 22:38):

I don't understand why by simp [x]; exact h makes this fast. With no imports, all it's doing is unfolding x.

Daniil Kisel (Nov 19 2023 at 22:43):

Yes, works in an instant, thanks!

Alex J. Best (Nov 19 2023 at 22:44):

yeah indeed id h also works instantly :mind_blown:

Kyle Miller (Nov 19 2023 at 22:45):

Maybe it's somehow tricking it to unfold definitions rather than evaluating them on this particular x...

If you unfold all the definitions, you have

h: { val := Fin.ofNat 480.val.val } < 24
 (Fin.ofNat 480.val.val).val < 24

where the first is a UInt8 less-than.

Daniil Kisel (Nov 19 2023 at 23:17):

It seems like neither id nor simpa work in the original context, here is another example:

def f (h0 : Char): Unit :=
  let x := h0.val - '0'.val |>.toUInt8
  if h: x < 24
    then
      have hh : x.toNat < 24 := id h -- by simpa [x] using h
      ()
    else
      ()

Replacing h0.val - '0'.val with a constant or with h0.val makes it instant, making h0 constant is the same...

Alex J. Best (Nov 19 2023 at 23:25):

def f (h0 h1 : Char) : Unit :=
  let x := (h0.val - '0'.val) * 10 + (h1.val - '0'.val) |>.toUInt8
  if h: x < 24
    then
      have hh : x.toNat < 24 := by
        rw [UInt8.toNat]
        exact h
      ()
    else
      ()

works for me

Daniil Kisel (Nov 19 2023 at 23:31):

Works in the context, thanks again! But why does it loop in the first place, or when I replace rewrite with unfold?

Mario Carneiro (Nov 20 2023 at 00:38):

Profiler says that most of the time is spent in the kernel typechecker; I'm running it through lean4lean to get some more logging but it seems like Nat.mod is unfolded at some point and thereafter whnf tries to run the underlying WF definition

Mario Carneiro (Nov 20 2023 at 01:23):

Looks like the issue is that the kernel typechecking goal is

example :
  let x := UInt32.toUInt8 400
  UInt8.toNat x = Nat.mod (UInt32.toNat 400) (255 + 1) := by intro x; rfl

and the heuristic for determining whether to use the Nat.mod kernel accelerated definition is that both sides of the equality have no fvars, including fvars with definitions. So Nat.mod is unfolded instead, and thereafter comes 400 steps and a massive expression at each step


Last updated: Dec 20 2023 at 11:08 UTC