Zulip Chat Archive

Stream: general

Topic: show is slow


Kevin Buzzard (Aug 04 2018 at 11:37):

import data.real.basic

set_option profiler true

theorem works_fine : (3 : ) = ((3 : ) : ) := rfl
-- elaboration of works_fine took 31.9ms

theorem very_slow (n : ) (x : ) (H1 : x = n) (H2 : n < 3) : x < 3 :=
begin
  show x < ((3 : ) : ), -- this is the bad line
  rw H1,
  rwa int.cast_lt,
end

-- elaboration of very_slow took 5.4s

theorem much_faster (n : ) (x : ) (H1 : x = n) (H2 : n < 3) : x < 3 :=
begin
  rw (show (3 : ) = ((3 : ) : ), from rfl),
  rw H1,
  rwa int.cast_lt,
end
-- elaboration of much_faster took 61.6ms

Note one elaboration time is in seconds not milliseconds. What have I done wrong here? I have somehow misused show, it seems.

Kevin Buzzard (Aug 04 2018 at 11:38):

theorem very_slow (n : ) (x : ) (H1 : x = n) (H2 : n < (3 : )) : x < (3 : ) :=
begin
  show x < ((3 : ) : ), -- this is the bad line
  rw H1,
  rwa int.cast_lt,
end

doesn't fix it. I can't imagine what show is doing.

Mario Carneiro (Aug 04 2018 at 11:50):

does change do any better?

Kevin Buzzard (Aug 04 2018 at 11:50):

Progress:

import data.real.basic

set_option profiler true

theorem works_fine : (3 : ) = ((3 : ) : ) := rfl
-- elaboration of works_fine took 14.3ms

theorem very_slow2 : (3 : ) = ((3 : ) : ) :=
begin
  have H : (3 : ) = ((3 : ) : ) := rfl,
  exact H
end
-- elaboration of very_slow2 took 5.83s

Kevin Buzzard (Aug 04 2018 at 11:51):

(progress in the sense that the problem is now simpler and even more confusing)

Chris Hughes (Aug 04 2018 at 11:55):

Don't use definitional reduction for reals is probably the best solution.

theorem very_slow (n : ) (x : ) (H1 : x = n) (H2 : n < (3 : )) : x < (3 : ) :=
begin
  have : (3 : ) = ((3 : ) : ) := by simp,
  rwa [H1, this, int.cast_lt],
end

Kevin Buzzard (Aug 04 2018 at 11:56):

Sure there are workarounds, but what is surprising is that Lean is sometimes proving the result by rfl quickly and sometimes not

Kevin Buzzard (Aug 04 2018 at 11:57):

theorem very_slow3 : (3 : ) = ((3 : ) : ) :=
begin
  change (3 : ) with ((3 : ) : ),
  refl,
end

theorem very_slow4 : (3 : ) = ((3 : ) : ) :=
begin
  change ((3 : ) : ) with (3 : ),
  refl,
end

same problem with change

Kevin Buzzard (Aug 04 2018 at 12:01):

theorem very_slow : (3 : ) = ((3 : ) : ) :=
begin
  have H : (3 : ) = ((3 : ) : ) := rfl,
  exact H
end

theorem works_fine : (3 : ) = ((3 : ) : ) :=
begin
  have H : (3 : ) = ((3 : ) : ),
    refl,
  exact H,
end

Way beyond my pay grade

Mario Carneiro (Aug 04 2018 at 12:05):

I have narrowed it down to

run_cmd assertv_core `h `((3 : ℝ) = ((3 : ℤ) : ℝ)) `(eq.refl (3 : ℝ))

which is slow, while

run_cmd assertv_core `h `((3 : ℝ) = ((3 : ℤ) : ℝ))
  `(show (3 : ℝ) = ((3 : ℤ) : ℝ), from eq.refl (3 : ℝ))

is fast

Kenny Lau (Aug 04 2018 at 12:07):

how do I use run_cmd?

Mario Carneiro (Aug 04 2018 at 12:07):

it accepts a tactic A and runs it in a dummy state

Kenny Lau (Aug 04 2018 at 12:07):

import data.real.basic

run_cmd assertv_core `h `((3 : ) = ((3 : ) : )) `(eq.refl (3 : ))

Kenny Lau (Aug 04 2018 at 12:07):

this doesn't work

Mario Carneiro (Aug 04 2018 at 12:07):

it's basically the same as example : true := by tac

Mario Carneiro (Aug 04 2018 at 12:08):

open tactic

Kenny Lau (Aug 04 2018 at 12:08):

lol

Kenny Lau (Aug 04 2018 at 12:10):

run_cmd assertv_core `h `((2 : ) = ((3 : ) : ))
  `(show (3 : ) = ((3 : ) : ), from eq.refl (3 : ))

Kenny Lau (Aug 04 2018 at 12:10):

this is slow

Kenny Lau (Aug 04 2018 at 12:10):

this timed out

Kenny Lau (Aug 04 2018 at 12:10):

(it's 2 instead of 3 in the beginning)

Kenny Lau (Aug 04 2018 at 12:11):

while this is fast (I changed the last 3 to 2 instead):

run_cmd assertv_core `h `((3 : ) = ((3 : ) : ))
  `(show (3 : ) = ((3 : ) : ), from eq.refl (2 : ))

Kenny Lau (Aug 04 2018 at 12:11):

so the show is slow

Mario Carneiro (Aug 04 2018 at 12:14):

it's not a fair comparison when the statement isn't true though

Mario Carneiro (Aug 04 2018 at 12:15):

because then lean does completely different things with regard to error reporting and stuff

Kenny Lau (Aug 04 2018 at 12:15):

run_cmd assertv_core `h `((3 : ) = ((3 : ) : ))
  `(show (2 : ) = ((2 : ) : ), from eq.refl (2 : ))

Kenny Lau (Aug 04 2018 at 12:15):

the statement is true, and it is slow

Kenny Lau (Aug 04 2018 at 12:15):

oh wait, this is fast:

run_cmd assertv_core `h `((3 : ) = ((3 : ) : ))
  `(show (2 : ) = ((2 : ) : ), from eq.refl (3 : ))

Mario Carneiro (Aug 04 2018 at 12:15):

In both cases it's going to fail because the types don't match

Kenny Lau (Aug 04 2018 at 12:16):

but they expect 2 instead of 3!

Kenny Lau (Aug 04 2018 at 12:16):

type mismatch at application
  (λ (this : 2 = ↑2), this) (eq.refl 3)
term
  eq.refl 3
has type
  3 = 3
but is expected to have type
  2 = ↑2

Kenny Lau (Aug 04 2018 at 12:16):

unexpected argument at application
  eq.refl 3
given argument
  3
expected argument
  2

Mario Carneiro (Aug 04 2018 at 12:16):

assertv_core checks that the type of the last argument is the same as the second argument

Mario Carneiro (Aug 04 2018 at 12:16):

so all of the numbers have to match

Mario Carneiro (Aug 04 2018 at 12:19):

I think there is a bug in assertv_core. If I use assert_core instead, it works fine, which you can achieve by using the proof-omitted form of tactic have

Mario Carneiro (Aug 04 2018 at 12:19):

theorem not_slow : (3 : ) = ((3 : ) : ) :=
begin
  have H : (3 : ) = ((3 : ) : ), exact rfl,
  exact H
end

Mario Carneiro (Aug 04 2018 at 12:20):

note that definev_core is also slow, which is linked in to the let tactic

Kenny Lau (Aug 04 2018 at 12:22):

vm_obj assert_define_core(bool is_assert, name const & n, expr const & t, tactic_state const & s) {
    optional<metavar_decl> g = s.get_main_goal_decl();
    if (!g) return mk_no_goals_exception(s);
    type_context_old ctx     = mk_type_context_for(s);
    if (!is_sort(ctx.whnf(ctx.infer(t)))) {
        format msg("invalid ");
        if (is_assert) msg += format("assert"); else msg += format("define");
        msg += format(" tactic, expression is not a type");
        msg += pp_indented_expr(s, t);
        return tactic::mk_exception(msg, s);
    }
    local_context lctx   = g->get_context();
    expr new_M_1         = ctx.mk_metavar_decl(lctx, t);
    expr new_M_2, new_val;
    if (is_assert) {
        expr new_target  = mk_pi(n, t, g->get_type());
        new_M_2          = ctx.mk_metavar_decl(lctx, new_target);
        new_val          = mk_app(new_M_2, new_M_1);
    } else {
        expr new_target  = mk_let(n, t, new_M_1, g->get_type());
        new_M_2          = ctx.mk_metavar_decl(lctx, new_target);
        new_val          = new_M_2;
    }
    ctx.assign(head(s.goals()), new_val);
    list<expr> new_gs    = cons(new_M_1, cons(new_M_2, tail(s.goals())));
    return tactic::mk_success(set_mctx_goals(s, ctx.mctx(), new_gs));
}

vm_obj tactic_assert_core(vm_obj const & n, vm_obj const & t, vm_obj const & s) {
    return assert_define_core(true, to_name(n), to_expr(t), tactic::to_state(s));
}

vm_obj assertv_definev_core(bool is_assert, name const & n, expr const & t, expr const & v, tactic_state const & s) {
    optional<metavar_decl> g = s.get_main_goal_decl();
    if (!g) return mk_no_goals_exception(s);
    type_context_old ctx     = mk_type_context_for(s);
    expr v_type          = ctx.infer(v);
    if (!ctx.is_def_eq(t, v_type)) {
        auto thunk = [=]() {
            format msg("invalid ");
            if (is_assert) msg += format("assertv"); else msg += format("definev");
            msg += format(" tactic, value has type");
            msg += pp_indented_expr(s, v_type);
            msg += line() + format("but is expected to have type");
            msg += pp_indented_expr(s, t);
            return msg;
        };
        return tactic::mk_exception(thunk, s);
    }
    local_context lctx   = g->get_context();
    expr new_M, new_val;
    if (is_assert) {
        expr new_target  = mk_pi(n, t, g->get_type());
        new_M            = ctx.mk_metavar_decl(lctx, new_target);
        new_val          = mk_app(new_M, v);
    } else {
        expr new_target  = mk_let(n, t, v, g->get_type());
        new_M            = ctx.mk_metavar_decl(lctx, new_target);
        new_val          = new_M;
    }
    ctx.assign(head(s.goals()), new_val);
    list<expr> new_gs    = cons(new_M, tail(s.goals()));
    return tactic::mk_success(set_mctx_goals(s, ctx.mctx(), new_gs));
}

vm_obj tactic_assertv_core(vm_obj const & n, vm_obj const & e, vm_obj const & pr, vm_obj const & s) {
    return assertv_definev_core(true, to_name(n), to_expr(e), to_expr(pr), tactic::to_state(s));
}

Mario Carneiro (Aug 04 2018 at 12:24):

the issue is somewhere in assertv_definev_core, but I don't see anything wrong

Mario Carneiro (Aug 04 2018 at 12:24):

the ctx.is_def_eq call is potentially expensive, but you can test that in lean and it's not

Mario Carneiro (Aug 04 2018 at 12:36):

The mystery continues. As far as I can tell, the following lean code should do the same as assertv_definev_core in this case:

run_cmd do
  let t : expr := `((3 : ℝ) = ((3 : ℤ) : ℝ)),
  let v : expr := `(eq.refl (3:ℝ)),
  v_t ← infer_type v,
  is_def_eq t v_t,
  r ← target,
  let e := expr.pi `h binder_info.default t r,
  m ← mk_meta_var e,
  let e' := expr.app m v,
  exact e',
  set_goals [m]

yet it runs without any problems

Mario Carneiro (Aug 04 2018 at 13:21):

@Sebastian Ullrich Could you debug this for me? Here's a mathlib free version of the test:

structure Q := (num : )
def id' (n : ) : Q := n / n.gcd 1
instance : has_zero Q := ⟨⟨0⟩⟩
instance : has_one Q := ⟨⟨1⟩⟩
instance : has_add Q := ⟨λ n₁ n₂, id' (n₁ + n₂)

run_cmd tactic.try_for 1000 (assertv_core `h
  `((4 : Q) = 4+0)
  `(show (4 : Q) = 4+0, from eq.refl (4 : Q)) >> admit) --works

run_cmd tactic.try_for 10000 (assertv_core `h
  `((4 : Q) = 4+0)
  `(eq.refl (4 : Q)) >> admit) --timeout

Johan Commelin (Aug 04 2018 at 13:43):

The --works version has 1000, whereas the --timeout version has 10000. I don't know if that matters...

Mario Carneiro (Aug 04 2018 at 14:14):

That's probably not needed, but it is saying that the first completes in <1000 ms and the second does not complete with <10000 ms so it is much worse

Simon Hudon (Aug 04 2018 at 17:08):

Is this something norm_num would help with?

Simon Hudon (Aug 04 2018 at 17:10):

If I understand correctly, refl is slow because it's unfolding the numerals.

Kevin Buzzard (Aug 04 2018 at 17:12):

refl isn't always slow. It's just slow if you invoke it the wrong way :-)

Kevin Buzzard (Aug 04 2018 at 17:13):

You can prove that the real 3 is the coercion of the integer 3 using simp as well, which is quick (but not as quick as when you use refl, if you use refl the right way)

Mario Carneiro (Aug 04 2018 at 17:33):

I don't think rfl wins over simp here even in the "good" case, at least not if your numbers are moderately large. Calculating 4 : real directly requires a number of gcd calculations, which are slow


Last updated: Dec 20 2023 at 11:08 UTC