Skip to content
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

[Merged by Bors] - perf: make Decidable Injective approximately twice as fast #21975

Closed
wants to merge 6 commits into from

Conversation

lambda-fairy
Copy link
Collaborator

@lambda-fairy lambda-fairy commented Feb 17, 2025

The existing implementation takes the definition (∀ x y, f x = f y → x = y) directly. This is suboptimal because:

  1. Every pair of values is checked twice (both (x, y) and (y, x))
  2. The diagonals (x, x) are checked redundantly
  3. The outputs of f are not cached, which might be a problem if f is expensive

This PR proposes a new implementation of Decidable Injective and Decidable InjOn. It collects the outputs in a Multiset, then decides Multiset.Nodup. Because Nodup is built upon List.Pairwise, which only checks unordered non-diagonal pairs, the above problems are addressed automatically.

This also allows for dropping DecidableEq on the input type, as that was to work around the diagonal case.

Thank you @edegeltje and @plp127 for suggesting List.Pairwise, and many other people in the Zulip thread for the discussion around Sym2 (which ended up being unused here, but was still valuable).


Open in Gitpod

Copy link

github-actions bot commented Feb 17, 2025

PR summary 1f00aeb576

Import changes for modified files

No significant changes to the import graph

Import changes for all files
Files Import difference

Declarations diff

+ nodup_map_iff_injOn
+ nodup_map_univ_iff_injective

You can run this locally as follows
## summary with just the declaration names:
./scripts/declarations_diff.sh <optional_commit>

## more verbose report:
./scripts/declarations_diff.sh long <optional_commit>

The doc-module for script/declarations_diff.sh contains some details about this script.


No changes to technical debt.

You can run this locally as

./scripts/technical-debt-metrics.sh pr_summary
  • The relative value is the weighted sum of the differences with weight given by the inverse of the current value of the statistic.
  • The absolute value is the relative value divided by the total sum of the inverses of the current values (i.e. the weighted average of the differences).

@github-actions github-actions bot added the t-data Data (lists, quotients, numbers, etc) label Feb 17, 2025
@lambda-fairy lambda-fairy marked this pull request as ready for review February 17, 2025 07:57
@lambda-fairy
Copy link
Collaborator Author

What's the standard way to benchmark a theorem?

I did a quick test with the snippet below, and did confirm that the old code is ~ 2x slower, but it would be nice to be more precise about this.

def testFn (i : Fin 100) : Fin 100 := i + 42

set_option maxRecDepth 100000 in
example : Function.Injective testFn := by decide

Copy link
Collaborator

@grunweg grunweg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments: the implementation looks straightforward; I haven't really thought if there are better options.

@leanprover-community-bot-assistant leanprover-community-bot-assistant added the merge-conflict The PR has a merge conflict with master, and needs manual merging. (this label is managed by a bot) label Feb 17, 2025
@github-actions github-actions bot removed the merge-conflict The PR has a merge conflict with master, and needs manual merging. (this label is managed by a bot) label Feb 18, 2025
@lambda-fairy
Copy link
Collaborator Author

Fixed conflicts from #21831.

@lambda-fairy lambda-fairy requested a review from grunweg February 18, 2025 06:54
@grunweg grunweg changed the title feat: Make Decidable Injective approximately twice as fast perf: make Decidable Injective approximately twice as fast Feb 18, 2025
@grunweg
Copy link
Collaborator

grunweg commented Feb 18, 2025

Do we want better benchmarking to confirm this? Otherwise, this looks good to me. Thanks for PRing it!
maintainer merge?

Copy link

🚀 Pull request has been placed on the maintainer queue by grunweg.

Copy link
Collaborator

@j-loreaux j-loreaux left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this seems straightforward and the benefit is self-evident, I'm hesitant to merge this myself because I don't know if it's possible to create diamonds here. Of course, it's a Subsingleton, so it shouldn't be too much of an issue, but I want to be sure. For that reason:

maintainer merge

Copy link

🚀 Pull request has been placed on the maintainer queue by j-loreaux.

@lambda-fairy lambda-fairy removed the request for review from grunweg February 19, 2025 03:07
@urkud
Copy link
Member

urkud commented Feb 19, 2025

It's a pity we can't have a more efficient implementation if the codomain is a LinearOrder (this would create a non-defeq diamond).
Thanks!
bors merge

@leanprover-community-mathlib4-bot leanprover-community-mathlib4-bot added ready-to-merge This PR has been sent to bors. and removed maintainer-merge labels Feb 19, 2025
mathlib-bors bot pushed a commit that referenced this pull request Feb 19, 2025
The existing implementation takes the definition (`∀ x y, f x = f y → x = y`) directly. This is suboptimal because:

1. Every pair of values is checked twice (both `(x, y)` and `(y, x)`)
2. The diagonals `(x, x)` are checked redundantly
3. The outputs of `f` are not cached, which might be a problem if `f` is expensive

This PR proposes a new implementation of `Decidable Injective` and `Decidable InjOn`. It collects the outputs in a `Multiset`, then decides `Multiset.Nodup`. Because `Nodup` is built upon `List.Pairwise`, which only checks unordered non-diagonal pairs, the above problems are addressed automatically.

This also allows for dropping `DecidableEq` on the input type, as that was to work around the diagonal case.

Thank you @edegeltje and @plp127 for suggesting `List.Pairwise`, and many other people in the [Zulip thread] for the discussion around `Sym2` (which ended up being unused here, but was still valuable).

[Zulip thread]: https://leanprover.zulipchat.com/#narrow/channel/287929-mathlib4/topic/Idea.3A.20Helper.20for.20speeding.20up.20Decidable.20on.20symmetric.20props
@mathlib-bors
Copy link
Contributor

mathlib-bors bot commented Feb 19, 2025

Pull request successfully merged into master.

Build succeeded:

@mathlib-bors mathlib-bors bot changed the title perf: make Decidable Injective approximately twice as fast [Merged by Bors] - perf: make Decidable Injective approximately twice as fast Feb 19, 2025
@mathlib-bors mathlib-bors bot closed this Feb 19, 2025
@mathlib-bors mathlib-bors bot deleted the lambda-fairy/fast-injective branch February 19, 2025 08:27
lambda-fairy added a commit to lambda-fairy/taleve that referenced this pull request Feb 19, 2025
@lambda-fairy
Copy link
Collaborator Author

lambda-fairy commented Feb 19, 2025

With this code and this commit

set_option profiler true in
set_option profiler.threshold 10 in
theorem pieces_injective_of_ne_O {p : Piece} (hp : p ≠ .O) : Function.Injective (pieces p) := by
  cases p
  case O => contradiction
  all_goals decide

Before:

tactic execution of Lean.Parser.Tactic.decide took 79.6ms
tactic execution of Lean.Parser.Tactic.decide took 79.2ms
tactic execution of Lean.Parser.Tactic.decide took 69.8ms
tactic execution of Lean.Parser.Tactic.decide took 69.8ms
tactic execution of Lean.Parser.Tactic.decide took 70.9ms
tactic execution of Lean.Parser.Tactic.decide took 70.1ms
tactic execution of Lean.Parser.Tactic.tacticSeq1Indented took 14.1ms
type checking took 782ms

After:

tactic execution of Lean.Parser.Tactic.decide took 35.3ms
tactic execution of Lean.Parser.Tactic.decide took 28.9ms
tactic execution of Lean.Parser.Tactic.decide took 16.2ms
tactic execution of Lean.Parser.Tactic.decide took 15.5ms
tactic execution of Lean.Parser.Tactic.decide took 14.9ms
tactic execution of Lean.Parser.Tactic.decide took 15.1ms
type checking took 170ms

So I'm very happy with this change 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready-to-merge This PR has been sent to bors. t-data Data (lists, quotients, numbers, etc)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants