Skip to content

feat: Generalize type of step indices with metaprogramming and typeclasses - #576

Open
markusdemedeiros wants to merge 53 commits into
masterfrom
sidx-defaults
Open

feat: Generalize type of step indices with metaprogramming and typeclasses #576
markusdemedeiros wants to merge 53 commits into
masterfrom
sidx-defaults

Conversation

@markusdemedeiros

@markusdemedeiros markusdemedeiros commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

This PR builds off of Alvin's work in #550, as well as Mario's work and my own attempt. I believe it solves every single problem reported with the prior attempts at this feature. The text here is long because I will try to explain how.

TL;DR:

  • A strictly isolated typeclass restores the old outParam behaviors around local class inference.
  • It does not violate the global typeclass rules for our unbundled algebraic hierarchy.
  • A lightweight command elab guards against common errors.
  • No duplicate or scoped notation needed and essentially no step index type annotations (the single exception is OFE.eq_dist: see limitations below).
  • No bundling, automation is unchanged.
  • Fully opt-in, hierarchy behaves normally when turned off. Working without default indices enabled allows multiple (non-overlapping) SIdx classes even in the same theorem.

In PR #550 I mentioned that having local scoped instances that could depend on section variables would solve all of our problems. This PR started by trying to emulate that, and morphed into something I think might make everybody 99%-100% happy.

Technique

The trick is to have a special typeclasss for default SIdx implementations, and let that provide the default instance for SIdx. Concretely, we start with the normal implicit index version of OFE (similar for COFE, OFunctor, etc)

@[rocq_alias ofe]
class OFE {SI : Type _} [SIdx SI] (α : Type _) where
  Dist : SI → α → α → Prop
  -- ... 

however, we add a second class for declaring default step index instances with an outParam index, and let it be the default instance for the step index type

class DefaultSI (SI : outParam (Type u)) where
  private mk ::
  sidx : SIdx SI

@[default_instance, reducible]
def dfltSIdx {SI : Type u} [d : DefaultSI SI] : SIdx SI := d.sidx

Initially I did this to put different defaults in different scopes (so we could emulate scoped default instances) but I ended up deciding that an elab was a better way to control it. To that end, the stepindex command puts an instance of the DefaultSI class in scope and does some sanity checks to ensure that it is the only one at elab time:

scoped stepindex Nat

Ordinal works in IrisMath too. Unlike hypothetical scoped default instances, the default index type can depend on section variables, so your type of step indices can be defined from any other Lean machinery. Here is how to generalize a section to use a generic type of step indices, such that the SIdx constraint on the type obeys normal typeclass synthesis rules:

variable {SI : Type _} [SIdx SI]
local stepindex SI

Hierarchy

Once a stepindex is declared, a DefaultSI instance is put in scope, so plain typeclass synthesis will handle filling in the SI type and the [SIdx SI] instances almost exactly like it would have in the outParam approach. However there are big improvements related to how stable this is. Synthesis of this arbitrary instance happens once, at the time the local stepindex command is elab'd, and in the happy path this instance will be synthesized using normal synthesis rules (ie. it will pick the [SIdx SI] instance for SI in the snippet above, not an arbitrary instance at every call site). Even if you get it wrong, and break the hierarchy in some other way, because synthesis happens only once this choice is guaranteed to be self-consistent for the rest of the section.

I've also used the module system to make declaring DefaultSI instances out of band difficult. It's not impossible, I don't think, but I think it is very hard to do by accident. Compare this to accidentally copy-pasting an open statement that includes a scope you didn't realize had a SIdx instance in it: the latter is much easier to get wrong. Setting a global step index type is also disabled.

Basic hierarchy discipline like avoiding non-definitional diamonds still applies of course, but it is no longer possible for an unrelated SIdx typeclass instance to break unrelated OFE synthesis. The stepindex command also has an option to provide an explicit instance name: I suspect this is unnecessary, but it gives you to turn off even the command elab-time nondeterminism if such a need arises. The final hierarchy thing I'll mention is that, unlike the outParam appraoches, the default step indices are fully opt-in. I can use OFE/COFE/OFunctor/... without interacting setting default step indices at all, and it behaves like any other two-parameter Lean typeclass. You can use multiple index types in the same theorem if you want. The stability of our algebraic hierarchy is preserved with or without default step indices set.

Notations

Because of the outParam, you get nearly every nicety of Alvin's original approaches, and adapting old code is nearly zero work. OFE does not need to specify its SI parameter, and step indices (even 0) do not need to specify their type. I also reused the stepindex% term elab from my other attempt: this elaborates to the current default step index type or a hole if none is set. However, unlike my prior approach, this is not used for any odd optParam stuff: it's used a total of three times so that we can have reuse the old notation and make it fill in the SI type at its elab site:

scoped notation:40 x " ≡{" n "}≡ " y:41 => OFE.Dist n x y (SI := stepindex%)

Doing it this way avoids the need for the duplicate scoped notations as in Mario's version. I didn't even define notation for adding in the type SI because I think at this point it is not necessary.

Limitation

The only edge case I could not resolve was for eq_dist (and a few one-offs like it): this is an annoying combination of both not being a notation, and not referencing the step index type, so neither typeclass inference nor an optParam can be used to fill the type. Admittedly, the outParam version (nondetermistically) fills this in at every call site where mind doesn't.

In this PR I explicitly fixed each SI instance be Nat or SI as at each call site so that the Lean code looks normal. I could have also defined eq_dist' to be eq_dist fixing (SI := stepindex%) with an optParam and it would be uniform across the repo. I'm open to any solutions to this problem.

Conclusion

I hope this version meets everybody's requirements! I think this solution is more than the sum of its parts, and cards on the table, I'm very pleased with it. To me it feels quite inline with other Lean features. I look forward to your feedback :)

cc: @alvinylt @MackieLoeffel @Kaptch @digama0

alvinylt added 30 commits July 28, 2026 11:51
Replace all proofs in section `Fixpoint`, all requires rewrite
Parametrisation of `CMRA` to be done in a future PR
@MackieLoeffel

Copy link
Copy Markdown
Collaborator

I merged master into this branch and resolved the conflicts. Something is broken with the Display tests, which I don't understand. @alvinylt Can you look into this?

@markusdemedeiros

Copy link
Copy Markdown
Collaborator Author

(I'm doing a pass and will also fix the IrisMath.Numbers build)

@alvinylt

Copy link
Copy Markdown
Contributor

Something is broken with the Display tests

Do you mean Tests/Display.lean? All tests seem to work fine for me.

@MackieLoeffel

Copy link
Copy Markdown
Collaborator

Do you mean Tests/Display.lean? All tests seem to work fine for me.

Very weird. It seems like I got my local copy into a weird state where the test would fail. A lake clean solved this problem.

I am done with my cleanup pass over this PR. From my side, the only thing that is missing is moving the local stepindex annotations to the top of the file.

@MackieLoeffel

Copy link
Copy Markdown
Collaborator

!bench

@leanprover-radar

leanprover-radar commented Aug 13, 2026

Copy link
Copy Markdown

Benchmark results for ece2feb against fa57ead are in. There are significant results. @MackieLoeffel

  • 🟥 build//instructions: +119.9G (+6.16%)

Large changes (5🟥)

  • 🟥 build/module/Iris.Algebra.CMRA//instructions: +6.3G (+30.39%)
  • 🟥 build/module/Iris.Algebra.COFESolver//instructions: +12.0G (+94.03%)
  • 🟥 build/module/Iris.Algebra.OFE//instructions: +27.0G (+140.16%)
  • 🟥 build/module/Iris.Examples.Fix//instructions: +5.6G (+121.11%)
  • and 1 hidden

Medium changes (18🟥)

  • 🟥 build/module/Iris.Algebra.Agree//instructions: +2.1G (+27.10%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Csum//instructions: +2.6G (+12.60%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Excl//instructions: +1.1G (+25.49%)
  • 🟥 build/module/Iris.Algebra.GenMap//instructions: +2.0G (+22.86%)
  • 🟥 build/module/Iris.Algebra.Heap//instructions: +3.6G (+27.21%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.HeapView//instructions: +1.5G (+8.74%)
  • 🟥 build/module/Iris.Algebra.IProp//instructions: +1.4G (+39.58%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.List//instructions: +2.0G (+35.71%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.View//instructions: +2.6G (+11.24%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.InternalEq//instructions: +1.9G (+28.18%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.Lib.FixpointBanach//instructions: +1.4G (+44.89%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.MonPred//instructions: +1.4G (+9.84%)
  • 🟥 build/module/Iris.Examples.IProp//instructions: +1.5G (+29.36%)
  • 🟥 build/module/Iris.Instances.IProp.Instance//instructions: +2.8G (+17.10%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Instances.Lib.Boxes//instructions: +2.9G (+13.80%)
  • 🟥 build/module/Iris.Instances.Lib.GhostMap//instructions: +4.0G (+8.49%)
  • 🟥 build/module/Iris.Instances.Lib.SavedProp//instructions: +2.8G (+42.41%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Instances.Lib.WSat//instructions: +1.4G (+16.70%)

Small changes (34🟥)

  • 🟥 build/module/Iris.Algebra.Auth//instructions: +715.7M (+9.27%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.BigOp//instructions: +678.9M (+6.12%)
  • 🟥 build/module/Iris.Algebra.DFrac//instructions: +335.3M (+2.02%)
  • 🟥 build/module/Iris.Algebra.DynReservationMap//instructions: +744.9M (+7.97%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Functions//instructions: +356.3M (+11.25%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Lib.DFracAgree//instructions: +279.8M (+9.15%)
  • 🟥 build/module/Iris.Algebra.Lib.ExclAuth//instructions: +299.2M (+9.14%)
  • 🟥 build/module/Iris.Algebra.Lib.FracAuth//instructions: +227.7M (+3.65%)
  • 🟥 build/module/Iris.Algebra.Lib.UFracAuth//instructions: +253.2M (+4.68%)
  • 🟥 build/module/Iris.Algebra.LocalUpdates//instructions: +155.0M (+3.58%)
  • 🟥 build/module/Iris.Algebra.Numbers//instructions: +531.2M (+8.68%)
  • 🟥 build/module/Iris.Algebra.ReservationMap//instructions: +841.9M (+10.72%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.StepIndex//instructions: +1.9G (+52.79%) (reduced significance based on *//lines)
  • 🟥 build/module/Iris.Algebra.UPred//instructions: +455.1M (+14.07%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.BI//instructions: +220.6M (+6.30%)
  • 🟥 build/module/Iris.BI.BigOp.BigOrList//instructions: +122.3M (+3.20%)
  • 🟥 build/module/Iris.BI.BigOp.BigSepList//instructions: +395.2M (+2.31%)
  • 🟥 build/module/Iris.BI.Cmra//instructions: +152.1M (+4.13%)
  • 🟥 build/module/Iris.BI.DerivedLaws//instructions: +825.1M (+3.75%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.DerivedLawsLater//instructions: +305.8M (+3.38%)
  • and 14 more

@MackieLoeffel

Copy link
Copy Markdown
Collaborator

🟥 build/module/Iris.Algebra.OFE//instructions: +27.0G (+140.16%)

I have to say that I am a bit worried about this. OFE is the only file that is generic over the stepindex right now. If all files become twice as slow when we make them parametric over the step index, this would be not so great. It seems like this is due to the added typeclass search, but I have to look into this more.

@markusdemedeiros

Copy link
Copy Markdown
Collaborator Author

My pass is done. All local stepindex annotations are at the top of their respective files and there's only one per file.

I have to say that I am a bit worried about this. OFE is the only file that is generic over the stepindex right now. If all files become twice as slow when we make them parametric over the step index, this would be not so great. It seems like this is due to the added typeclass search, but I have to look into this more.

I think 2x would be not great, but it could also be worse than 2x. Perhaps we should ask around on the Zulip about this?

@markusdemedeiros

Copy link
Copy Markdown
Collaborator Author

I wonder if there's another file we could generalize to see if the pattern holds (though, we might have to generalize CMRA first to experiment with that, which seems like a big job)

@Kaptch

Kaptch commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Do we have any data on how big was the slowdown in the Rocq version?

@markusdemedeiros

Copy link
Copy Markdown
Collaborator Author

A superlinear slowdown would worry me enough to rethink the mechanism used in this PR. A 2x slowdown would not so much. This PR is about 10s faster than Iris-Rocq atm and even with a 2x slowdown we might be able to sqeeze it back under (of course, if there's a substantially faster implementation of local stepindex that would be better). I might set an agent on generic performance improvements just to figure out how much speed is actually lying on the table.

@MackieLoeffel

Copy link
Copy Markdown
Collaborator

!bench

@leanprover-radar

leanprover-radar commented Aug 13, 2026

Copy link
Copy Markdown

Benchmark results for 782f99f against a5f3796 are in. There are significant results. @MackieLoeffel

  • 🟥 build//instructions: +196.2G (+11.20%)

Large changes (5🟥)

  • 🟥 build/module/Iris.Algebra.CMRA//instructions: +7.2G (+36.40%)
  • 🟥 build/module/Iris.Algebra.COFESolver//instructions: +12.9G (+108.99%)
  • 🟥 build/module/Iris.Algebra.OFE//instructions: +26.4G (+144.19%)
  • 🟥 build/module/Iris.Examples.Fix//instructions: +5.9G (+154.85%)
  • and 1 hidden

Medium changes (33🟥)

  • 🟥 build/module/Iris.Algebra.Agree//instructions: +2.9G (+41.25%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Auth//instructions: +1.6G (+22.66%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.BigOp//instructions: +1.6G (+15.47%)
  • 🟥 build/module/Iris.Algebra.Csum//instructions: +3.5G (+18.02%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.DFrac//instructions: +1.2G (+7.61%)
  • 🟥 build/module/Iris.Algebra.DynReservationMap//instructions: +1.6G (+18.94%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Excl//instructions: +1.9G (+56.18%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Functions//instructions: +1.2G (+50.68%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.GenMap//instructions: +2.8G (+35.61%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Heap//instructions: +4.5G (+35.80%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.HeapView//instructions: +2.4G (+14.59%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.IProp//instructions: +2.2G (+79.29%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Lib.DFracAgree//instructions: +1.1G (+50.28%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Lib.ExclAuth//instructions: +1.1G (+43.38%)
  • 🟥 build/module/Iris.Algebra.Lib.FracAuth//instructions: +1.0G (+19.31%)
  • 🟥 build/module/Iris.Algebra.Lib.UFracAuth//instructions: +1.1G (+23.32%)
  • 🟥 build/module/Iris.Algebra.List//instructions: +2.7G (+58.52%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.LocalUpdates//instructions: +1.0G (+29.47%)
  • 🟥 build/module/Iris.Algebra.Numbers//instructions: +1.4G (+27.01%)
  • 🟥 build/module/Iris.Algebra.ReservationMap//instructions: +1.6G (+23.53%) (reduced significance based on absolute threshold)
  • and 13 more

Small changes (136🟥)

  • 🟥 build/module/Iris.Algebra.Frac//instructions: +922.9M (+21.11%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.IsOp//instructions: +860.6M (+62.55%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.LeibnizSet//instructions: +961.6M (+16.02%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Lib.MonoNat//instructions: +891.2M (+25.72%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Lib//instructions: +734.5M (+61.25%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Monoid//instructions: +915.0M (+68.56%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Mra//instructions: +891.9M (+43.59%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.StepIndex//instructions: +2.8G (+97.78%) (reduced significance based on *//lines)
  • 🟥 build/module/Iris.Algebra.UFrac//instructions: +915.2M (+34.94%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra.Updates//instructions: +898.2M (+27.30%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.Algebra//instructions: +746.5M (+62.77%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.Algebra//instructions: +677.8M (+11.38%)
  • 🟥 build/module/Iris.BI.BI//instructions: +682.6M (+22.43%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.BigOp.BigAndList//instructions: +569.1M (+16.73%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.BigOp.BigAndMap//instructions: +595.5M (+15.79%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.BigOp.BigOp//instructions: +822.8M (+5.03%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.BigOp.BigOrList//instructions: +570.7M (+16.92%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.BigOp.BigSepList//instructions: +973.1M (+5.89%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.BigOp.BigSepMSet//instructions: +573.4M (+13.18%) (reduced significance based on absolute threshold)
  • 🟥 build/module/Iris.BI.BigOp.BigSepMap//instructions: +636.5M (+7.98%)
  • and 116 more

@MackieLoeffel

Copy link
Copy Markdown
Collaborator

I am currently experimenting with an alternative version of the typeclass hierarchy in #627 . It seems to cut the typeclass synthesis time in OFE.lean in half, but it requires more care that we don't have duplicate instances in the context. I will post a message once I am done with my experiments and have something to report.

@MackieLoeffel

Copy link
Copy Markdown
Collaborator

I've looked into this and I have good news and bad news. The good news is that there is a version of this generalization that works and where the slowdown is significantly lower for OFE.lean (and presumably all generalized code). The bad news is that this is the direct outParam version that assumes that there is at most one instance of the SIdx typeclass imported. I've tried finding different versions that don't have a large slowdown but also avoid using an outParam on SIdx directly, but I was not able to do so. In the rest of this comment, I am summarizing my findings and we can then discuss, how we want to proceed.

Suspected cause of the slowdown in #576

My suspicion for the cause of the slowdown of the version in this PR is that using SIdx with a meta variable (e.g., when mentioning an OFE generalized over SI) causes a lot of failing typeclass searches.
For example, the following

set_option trace.Meta.synthInstance true in
theorem dist_equivalence [OFE α] {n : SI} : Equivalence (Dist (α := α) n) := dist_eqv

Causes the following typeclass trace, just for [OFE α]

[Meta.synthInstance] 💥️ SIdx ?m.1
  [Meta.synthInstance] ✅️ new goal SIdx ?m.1
    [Meta.synthInstance.instances] #[instSI]
  [Meta.synthInstance.apply] 💥️ apply instSI to SIdx ?m.1
    [Meta.synthInstance.tryResolve] 💥️ SIdx ?m.1 ≟ SIdx SI
[Meta.synthInstance] 💥️ SIdx ?m.2
  [Meta.synthInstance] ✅️ new goal SIdx ?m.2
    [Meta.synthInstance.instances] #[instSI]
  [Meta.synthInstance.apply] 💥️ apply instSI to SIdx ?m.2
    [Meta.synthInstance.tryResolve] 💥️ SIdx ?m.2 ≟ SIdx SI
[Meta.synthInstance] 💥️ SIdx ?m.2
  [Meta.synthInstance] ✅️ new goal SIdx ?m.2
    [Meta.synthInstance.instances] #[instSI]
  [Meta.synthInstance.apply] 💥️ apply instSI to SIdx ?m.2
    [Meta.synthInstance.tryResolve] 💥️ SIdx ?m.2 ≟ SIdx SI
[Meta.synthInstance] 💥️ SIdx ?m.2
  [Meta.synthInstance] ✅️ new goal SIdx ?m.2
    [Meta.synthInstance.instances] #[instSI]
  [Meta.synthInstance.apply] 💥️ apply instSI to SIdx ?m.2
    [Meta.synthInstance.tryResolve] 💥️ SIdx ?m.2 ≟ SIdx SI
[Meta.synthInstance] 💥️ SIdx ?m.2
  [Meta.synthInstance] ✅️ new goal SIdx ?m.2
    [Meta.synthInstance.instances] #[instSI]
  [Meta.synthInstance.apply] 💥️ apply instSI to SIdx ?m.2
    [Meta.synthInstance.tryResolve] 💥️ SIdx ?m.2 ≟ SIdx SI
[Meta.synthInstance] 💥️ SIdx ?m.2
  [Meta.synthInstance] ✅️ new goal SIdx ?m.2
    [Meta.synthInstance.instances] #[instSI]
  [Meta.synthInstance.apply] 💥️ apply instSI to SIdx ?m.2
    [Meta.synthInstance.tryResolve] 💥️ SIdx ?m.2 ≟ SIdx SI
[Meta.synthInstance] ✅️ DefaultSI SI
  [Meta.synthInstance] ✅️ new goal DefaultSI _tc.1
    [Meta.synthInstance.instances] #[@instDefaultSI_Iris_Algebra_OFE]
  [Meta.synthInstance.apply] ✅️ apply @instDefaultSI_Iris_Algebra_OFE to DefaultSI ?m.12
    [Meta.synthInstance.tryResolve] ✅️ DefaultSI ?m.12 ≟ DefaultSI ?m.12
    [Meta.synthInstance] ✅️ new goal SIdx _tc.1
      [Meta.synthInstance.instances] #[instSI]
  [Meta.synthInstance.apply] ✅️ apply instSI to SIdx SI
    [Meta.synthInstance.tryResolve] ✅️ SIdx SI ≟ SIdx SI
    [Meta.synthInstance.answer] ✅️ SIdx SI
  [Meta.synthInstance.resume] ✅️ propagating SIdx SI to subgoal SIdx SI of DefaultSI SI
    [Meta.synthInstance.resume] size: 1
    [Meta.synthInstance.answer] ✅️ DefaultSI SI
  [Meta.synthInstance] result instDefaultSI_Iris_Algebra_OFE

These failing typeclass searches appear for every SIdx SI where SI is a meta variable, which is very often. So my suspicion is that these failures cause a significant slowdown.

Alternative version using outparam #627

In #627, I did an experiment with an alternative version where SIdx SI has SI as an outParam directly, without a DefaultSI class.
This version avoids the failing typeclass searches, giving this trace for the same code as above:

[Meta.synthInstance] ✅️ SIdx SI
  [Meta.synthInstance] ✅️ new goal SIdx _tc.1
    [Meta.synthInstance.instances] #[instSI]
  [Meta.synthInstance.apply] ✅️ apply instSI to SIdx SI
    [Meta.synthInstance.tryResolve] ✅️ SIdx SI ≟ SIdx SI
    [Meta.synthInstance.answer] ✅️ SIdx SI
  [Meta.synthInstance] result instSI
[Meta.synthInstance] ✅️ SIdx SI
  [Meta.synthInstance] ✅️ new goal SIdx _tc.1
    [Meta.synthInstance.instances] #[instSI]
  [Meta.synthInstance.apply] ✅️ apply instSI to SIdx SI
    [Meta.synthInstance.tryResolve] ✅️ SIdx SI ≟ SIdx SI
    [Meta.synthInstance.answer] ✅️ SIdx SI
  [Meta.synthInstance] result instSI

Performance comparison

Here are the performance numbers of the different versions. All comparisons use commit a5f3796 as the base commit of the master branch.

Bench of #576 against master
🟥 build//instructions: +196.2G (+11.20%)
🟥 build/module/Iris.Algebra.OFE//instructions: +26.4G (+144.19%)

Bench of #627 against master
🟥 build//instructions: +139.9G (+7.99%)
🟥 build/module/Iris.Algebra.OFE//instructions: +12.7G (+69.24%)

Comparison of #627 directly against #576
build//instructions: -65.0G (-3.34%)
build/module/Iris.Algebra.OFE//instructions: -13.7G (-30.68%)

Since these performance numbers are always very abstract, I also profiled OFE.lean locally for the different branches using time lake env lean --profile Iris/Algebra/OFE.lean. Here are the results:

master

import took 183ms
cumulative profiling times:
	attribute application 30.3ms
	blocked (unaccounted) 414ms
	compilation (IR) 4.65ms
	compilation (LCNF base) 51.5ms
	compilation (LCNF impure) 17.8ms
	compilation (LCNF mono) 33.7ms
	congr simp thm 10.6ms
	dsimp 4.6ms
	elaboration 1.06s
	fix level params 7.17ms
	import 183ms
	initialization 19.4ms
	instantiate metavars 6.19ms
	interpretation 45.9ms
	let-to-have transformation 3.2ms
	linting 121ms
	module linting 0.00127ms
	parsing 85.1ms
	process pre-definitions 77.3ms
	share common exprs 14.7ms
	simp 263ms
	tactic execution 289ms
	type checking 352ms
	typeclass inference 368ms
lake env lean --profile Iris/Algebra/OFE.lean  3.21s user 0.17s system 135% cpu 2.503 total

#576

import took 391ms
cumulative profiling times:
	attribute application 40.3ms
	blocked (unaccounted) 529ms
	compilation (IR) 5.88ms
	compilation (LCNF base) 61.5ms
	compilation (LCNF impure) 23.7ms
	compilation (LCNF mono) 52.8ms
	congr simp thm 17.3ms
	dsimp 14.1ms
	elaboration 1.74s
	fix level params 11.3ms
	import 391ms
	initialization 18.3ms
	instantiate metavars 9.51ms
	interpretation 79.8ms
	let-to-have transformation 4.89ms
	linting 150ms
	module linting 0.000962ms
	parsing 107ms
	process pre-definitions 120ms
	share common exprs 21.3ms
	simp 292ms
	tactic execution 584ms
	type checking 688ms
	typeclass inference 2.21s
lake env lean --profile Iris/Algebra/OFE.lean  6.82s user 0.25s system 125% cpu 5.657 total

#627

import took 410ms
cumulative profiling times:
	attribute application 36.8ms
	blocked (unaccounted) 485ms
	compilation (IR) 6.07ms
	compilation (LCNF base) 54.6ms
	compilation (LCNF impure) 24.3ms
	compilation (LCNF mono) 50.7ms
	congr simp thm 17.4ms
	dsimp 13.7ms
	elaboration 1.49s
	fix level params 9.9ms
	import 410ms
	initialization 19ms
	instantiate metavars 8.4ms
	interpretation 78.5ms
	let-to-have transformation 3.9ms
	linting 151ms
	module linting 0.00112ms
	parsing 105ms
	process pre-definitions 108ms
	share common exprs 19.6ms
	simp 282ms
	tactic execution 506ms
	type checking 577ms
	typeclass inference 924ms
lake env lean --profile Iris/Algebra/OFE.lean  5.04s user 0.26s system 130% cpu 4.059 total

As one can see, there is a significant difference between all three versions. (2.503 total for master, 5.657 total for #576, and 4.059 total for #627)
Also the typeclass inference time differs quite a bit:
master: typeclass inference 368ms
#576: typeclass inference 2.21s
#627: typeclass inference 924ms

What now?

The following options come to mind:

A) Merge #576 as is and take the performance hit
pro: avoids having an outParam directly
con: has a significant performance impact, which might get worse when we generalize more files

B) Improve #627 with metaprogramming to give errors when the user imports two instances
pro: Noticably better performance than #576
con: needs some care that users cannot accidentally import two different instances of the SIdx typeclass and does not allow statements that mix two different SIdx.

C) Port more files to be generalized over the step index to see how big the performance impact is when more code is generalized.
pro: delays the decision until more information is available
con: delays the decision

D) Come up with some other version that has none of the downsides and all of the advantages
pro: Would be great
con: I don't know how to do this

My personal preference would be D) if someone has an idea, otherwise C). I hope this comment gives enough information that we can discuss this.

@markusdemedeiros

Copy link
Copy Markdown
Collaborator Author

Bummer! Thanks for digging into this, and I'll also say it was great foresight on your part to set up this radar stuff.

Indeed, I cannot think of a way around having a default instance (for its mvar setting abilities) and also a high priority instance. So any SIdx instance that is default will be tried last.

Can you trace where those 6 failing instSI instances in your trace are coming from? The thing I'm most worried about is if this 6 is secretly o(n). I'm also curious if they can be removed from the content via scoping. If careful scope management can solve the performance problem I think it would be a strictly better than an outParam approach: while both would require careful scope management, the idea I have in mind could locally opt-out with only a performance cost, instead of a global hierarchy stability or expressivity one. I'd still be cool with that.

As for just eating the cost: I would now not feel comfortable doing that without knowing what the long term implications are, especially if 6 is actually o(n). Maybe we should just try generalizing some more files and see what happens?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants