diff --git a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs
index b6e86e2b676..08e6d35a5f5 100644
--- a/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs
+++ b/compiler/rustc_infer/src/infer/lexical_region_resolve/mod.rs
@@ -137,6 +137,10 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> {
     ) -> LexicalRegionResolutions<'tcx> {
         let mut var_data = self.construct_var_data();
 
+        // Deduplicating constraints is shown to have a positive perf impact.
+        self.data.constraints.sort_by_key(|(constraint, _)| *constraint);
+        self.data.constraints.dedup_by_key(|(constraint, _)| *constraint);
+
         if cfg!(debug_assertions) {
             self.dump_constraints();
         }
@@ -183,7 +187,7 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> {
         let mut constraints = IndexVec::from_elem(Vec::new(), &var_values.values);
         // Tracks the changed region vids.
         let mut changes = Vec::new();
-        for constraint in self.data.constraints.keys() {
+        for (constraint, _) in &self.data.constraints {
             match *constraint {
                 Constraint::RegSubVar(a_region, b_vid) => {
                     let b_data = var_values.value_mut(b_vid);
@@ -678,7 +682,7 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> {
         let dummy_source = graph.add_node(());
         let dummy_sink = graph.add_node(());
 
-        for constraint in self.data.constraints.keys() {
+        for (constraint, _) in &self.data.constraints {
             match *constraint {
                 Constraint::VarSubVar(a_id, b_id) => {
                     graph.add_edge(NodeIndex(a_id.index()), NodeIndex(b_id.index()), *constraint);
@@ -885,9 +889,11 @@ impl<'cx, 'tcx> LexicalResolver<'cx, 'tcx> {
                     }
 
                     Constraint::RegSubVar(region, _) | Constraint::VarSubReg(_, region) => {
+                        let constraint_idx =
+                            this.constraints.binary_search_by(|(c, _)| c.cmp(&edge.data)).unwrap();
                         state.result.push(RegionAndOrigin {
                             region,
-                            origin: this.constraints.get(&edge.data).unwrap().clone(),
+                            origin: this.constraints[constraint_idx].1.clone(),
                         });
                     }
 
diff --git a/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs b/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs
index e06596df7b9..b2bcbbf2e53 100644
--- a/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs
+++ b/compiler/rustc_infer/src/infer/region_constraints/leak_check.rs
@@ -416,8 +416,8 @@ impl<'tcx> MiniGraph<'tcx> {
                 region_constraints.undo_log.region_constraints_in_snapshot(&snapshot.undo_snapshot)
             {
                 match undo_entry {
-                    AddConstraint(constraint) => {
-                        each_constraint(constraint);
+                    &AddConstraint(i) => {
+                        each_constraint(&region_constraints.data().constraints[i].0);
                     }
                     &AddVerify(i) => span_bug!(
                         region_constraints.data().verifys[i].origin.span(),
@@ -430,8 +430,8 @@ impl<'tcx> MiniGraph<'tcx> {
             region_constraints
                 .data()
                 .constraints
-                .keys()
-                .for_each(|constraint| each_constraint(constraint));
+                .iter()
+                .for_each(|(constraint, _)| each_constraint(constraint));
         }
     }
 
diff --git a/compiler/rustc_infer/src/infer/region_constraints/mod.rs b/compiler/rustc_infer/src/infer/region_constraints/mod.rs
index 5c043b1d3dd..c06adf085aa 100644
--- a/compiler/rustc_infer/src/infer/region_constraints/mod.rs
+++ b/compiler/rustc_infer/src/infer/region_constraints/mod.rs
@@ -20,7 +20,6 @@ use rustc_middle::ty::{ReBound, ReVar};
 use rustc_middle::ty::{Region, RegionVid};
 use rustc_span::Span;
 
-use std::collections::BTreeMap;
 use std::ops::Range;
 use std::{cmp, fmt, mem};
 
@@ -90,7 +89,7 @@ pub type VarInfos = IndexVec<RegionVid, RegionVariableInfo>;
 pub struct RegionConstraintData<'tcx> {
     /// Constraints of the form `A <= B`, where either `A` or `B` can
     /// be a region variable (or neither, as it happens).
-    pub constraints: BTreeMap<Constraint<'tcx>, SubregionOrigin<'tcx>>,
+    pub constraints: Vec<(Constraint<'tcx>, SubregionOrigin<'tcx>)>,
 
     /// Constraints of the form `R0 member of [R1, ..., Rn]`, meaning that
     /// `R0` must be equal to one of the regions `R1..Rn`. These occur
@@ -273,7 +272,7 @@ pub(crate) enum UndoLog<'tcx> {
     AddVar(RegionVid),
 
     /// We added the given `constraint`.
-    AddConstraint(Constraint<'tcx>),
+    AddConstraint(usize),
 
     /// We added the given `verify`.
     AddVerify(usize),
@@ -319,8 +318,9 @@ impl<'tcx> RegionConstraintStorage<'tcx> {
                 self.var_infos.pop().unwrap();
                 assert_eq!(self.var_infos.len(), vid.index());
             }
-            AddConstraint(ref constraint) => {
-                self.data.constraints.remove(constraint);
+            AddConstraint(index) => {
+                self.data.constraints.pop().unwrap();
+                assert_eq!(self.data.constraints.len(), index);
             }
             AddVerify(index) => {
                 self.data.verifys.pop();
@@ -443,14 +443,9 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> {
         // cannot add constraints once regions are resolved
         debug!("RegionConstraintCollector: add_constraint({:?})", constraint);
 
-        // never overwrite an existing (constraint, origin) - only insert one if it isn't
-        // present in the map yet. This prevents origins from outside the snapshot being
-        // replaced with "less informative" origins e.g., during calls to `can_eq`
-        let undo_log = &mut self.undo_log;
-        self.storage.data.constraints.entry(constraint).or_insert_with(|| {
-            undo_log.push(AddConstraint(constraint));
-            origin
-        });
+        let index = self.storage.data.constraints.len();
+        self.storage.data.constraints.push((constraint, origin));
+        self.undo_log.push(AddConstraint(index));
     }
 
     fn add_verify(&mut self, verify: Verify<'tcx>) {
diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs
index 13a09917c03..c6d029ddb65 100644
--- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs
+++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs
@@ -477,7 +477,7 @@ impl<'tcx> AutoTraitFinder<'tcx> {
         let mut vid_map: FxHashMap<RegionTarget<'cx>, RegionDeps<'cx>> = FxHashMap::default();
         let mut finished_map = FxHashMap::default();
 
-        for constraint in regions.constraints.keys() {
+        for (constraint, _) in &regions.constraints {
             match constraint {
                 &Constraint::VarSubVar(r1, r2) => {
                     {
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs
index e692f4ef72e..9de547ba6dc 100644
--- a/src/librustdoc/clean/auto_trait.rs
+++ b/src/librustdoc/clean/auto_trait.rs
@@ -195,7 +195,7 @@ where
         // into a map. Each RegionTarget (either a RegionVid or a Region) maps
         // to its smaller and larger regions. Note that 'larger' regions correspond
         // to sub-regions in Rust code (e.g., in 'a: 'b, 'a is the larger region).
-        for constraint in regions.constraints.keys() {
+        for (constraint, _) in &regions.constraints {
             match *constraint {
                 Constraint::VarSubVar(r1, r2) => {
                     {
diff --git a/tests/ui/higher-ranked/higher-ranked-lifetime-error.stderr b/tests/ui/higher-ranked/higher-ranked-lifetime-error.stderr
index d0892fd8b09..c25e731d962 100644
--- a/tests/ui/higher-ranked/higher-ranked-lifetime-error.stderr
+++ b/tests/ui/higher-ranked/higher-ranked-lifetime-error.stderr
@@ -4,8 +4,8 @@ error[E0308]: mismatched types
 LL |     assert_all::<_, &String>(id);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
    |
-   = note: expected reference `&String`
-              found reference `&String`
+   = note: expected trait `for<'a> <for<'a> fn(&'a String) -> &'a String {id} as FnMut<(&'a String,)>>`
+              found trait `for<'a> <for<'a> fn(&'a String) -> &'a String {id} as FnMut<(&'a String,)>>`
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/higher-ranked/trait-bounds/hrtb-perfect-forwarding.rs b/tests/ui/higher-ranked/trait-bounds/hrtb-perfect-forwarding.rs
index d45fa183c0c..07befeff43c 100644
--- a/tests/ui/higher-ranked/trait-bounds/hrtb-perfect-forwarding.rs
+++ b/tests/ui/higher-ranked/trait-bounds/hrtb-perfect-forwarding.rs
@@ -41,7 +41,7 @@ where
     // isize>`, we require `T : for<'a> Bar<&'a isize>`, but the where
     // clause only specifies `T : Bar<&'b isize>`.
     foo_hrtb_bar_not(&mut t);
-    //~^ ERROR implementation of `Bar` is not general enough
+    //~^ ERROR mismatched types
     //~^^ ERROR lifetime may not live long enough
 }
 
diff --git a/tests/ui/higher-ranked/trait-bounds/hrtb-perfect-forwarding.stderr b/tests/ui/higher-ranked/trait-bounds/hrtb-perfect-forwarding.stderr
index 727b9e6bec8..14a80630f40 100644
--- a/tests/ui/higher-ranked/trait-bounds/hrtb-perfect-forwarding.stderr
+++ b/tests/ui/higher-ranked/trait-bounds/hrtb-perfect-forwarding.stderr
@@ -53,14 +53,19 @@ note: due to current limitations in the borrow checker, this implies a `'static`
 LL |     T: for<'a> Foo<&'a isize> + Bar<&'b isize>,
    |        ^^^^^^^^^^^^^^^^^^^^^^
 
-error: implementation of `Bar` is not general enough
+error[E0308]: mismatched types
   --> $DIR/hrtb-perfect-forwarding.rs:43:5
    |
 LL |     foo_hrtb_bar_not(&mut t);
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Bar` is not general enough
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
    |
-   = note: `T` must implement `Bar<&'0 isize>`, for any lifetime `'0`...
-   = note: ...but it actually implements `Bar<&'1 isize>`, for some specific lifetime `'1`
+   = note: expected trait `for<'a> <T as Foo<&'a isize>>`
+              found trait `for<'a> <T as Foo<&'a isize>>`
+note: the lifetime requirement is introduced here
+  --> $DIR/hrtb-perfect-forwarding.rs:37:8
+   |
+LL |     T: for<'a> Foo<&'a isize> + Bar<&'b isize>,
+   |        ^^^^^^^^^^^^^^^^^^^^^^
 
 warning: function cannot return without recursing
   --> $DIR/hrtb-perfect-forwarding.rs:48:1
@@ -77,3 +82,4 @@ LL |       foo_hrtb_bar_hrtb(&mut t);
 
 error: aborting due to 2 previous errors; 4 warnings emitted
 
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/lifetimes/issue-79187-2.stderr b/tests/ui/lifetimes/issue-79187-2.stderr
index e8115bb6b06..0b7593f01c6 100644
--- a/tests/ui/lifetimes/issue-79187-2.stderr
+++ b/tests/ui/lifetimes/issue-79187-2.stderr
@@ -54,8 +54,13 @@ error[E0308]: mismatched types
 LL |     take_foo(|a: &i32| a);
    |     ^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
    |
-   = note: expected reference `&_`
-              found reference `&_`
+   = note: expected trait `for<'a> <{closure@$DIR/issue-79187-2.rs:11:14: 11:23} as FnOnce<(&'a i32,)>>`
+              found trait `for<'a> <{closure@$DIR/issue-79187-2.rs:11:14: 11:23} as FnOnce<(&'a i32,)>>`
+note: this closure does not fulfill the lifetime requirements
+  --> $DIR/issue-79187-2.rs:11:14
+   |
+LL |     take_foo(|a: &i32| a);
+   |              ^^^^^^^^^
 note: the lifetime requirement is introduced here
   --> $DIR/issue-79187-2.rs:5:21
    |
@@ -68,8 +73,13 @@ error[E0308]: mismatched types
 LL |     take_foo(|a: &i32| -> &i32 { a });
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
    |
-   = note: expected reference `&_`
-              found reference `&_`
+   = note: expected trait `for<'a> <{closure@$DIR/issue-79187-2.rs:14:14: 14:31} as FnOnce<(&'a i32,)>>`
+              found trait `for<'a> <{closure@$DIR/issue-79187-2.rs:14:14: 14:31} as FnOnce<(&'a i32,)>>`
+note: this closure does not fulfill the lifetime requirements
+  --> $DIR/issue-79187-2.rs:14:14
+   |
+LL |     take_foo(|a: &i32| -> &i32 { a });
+   |              ^^^^^^^^^^^^^^^^^
 note: the lifetime requirement is introduced here
   --> $DIR/issue-79187-2.rs:5:21
    |