Skip to content

#[derive(PartialEq)] does not reorder field comparisons to improve performance #141141

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

Closed
Sol-Ell opened this issue May 17, 2025 · 4 comments · Fixed by #141724
Closed

#[derive(PartialEq)] does not reorder field comparisons to improve performance #141141

Sol-Ell opened this issue May 17, 2025 · 4 comments · Fixed by #141724
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such I-slow Issue: Problems and improvements with respect to performance of generated code. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Sol-Ell
Copy link
Contributor

Sol-Ell commented May 17, 2025

Summary

The #[derive(PartialEq)] macro currently compares fields in declaration order, which can lead to suboptimal performance when expensive fields (like references or slices) are compared before cheap fields (like scalars).

This can cause unnecessary memory comparisons (memcmp/bcmp) to run even when a cheap field would have determined inequality early.


Reproducible Example

#[repr(C)]
#[derive(PartialEq, Eq)]
pub struct StructA<'a> {
    field_1: &'a str, // expensive to compare
    field_2: u8,      // cheap
}

#[repr(C)]
#[derive(PartialEq, Eq)]
pub struct StructB<'a> {
    field_1: u8,      // cheap
    field_2: &'a str, // expensive
}

pub fn compare_a(a: StructA, b: StructA) -> bool {
    a == b
}

pub fn compare_b(a: StructB, b: StructB) -> bool {
    a == b
}

Generated Assembly

  • compare_a compares the string (bcmp) before checking the u8.
  • compare_b checks the u8 first and avoids bcmp on early exit.

See Godbolt for live comparison: https://godbolt.org/z/7a41eT3P3


Meta

  • Rust version: stable (1.##) / nightly
  • Affects #[derive(PartialEq)] macro
  • Related: optimization of derived traits
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label May 17, 2025
@moxian

This comment has been minimized.

@rustbot rustbot added the C-discussion Category: Discussion or questions that doesn't represent real issues. label May 17, 2025
@kupiakos
Copy link
Contributor

kupiakos commented May 19, 2025

gives the author clear control over the semantics of the comparison

The current documentation for derive(PartialEq) does not state the order in which the comparisons occur, unlike derive(PartialOrd). Also, neither of them state whether short-circuiting takes place. PartialEq only states that two structs are equal if their fields are equal. Depending on ordered comparisons (or short-circuiting) in the derive today for semantics is depending on an undocumented behavior and that should be avoided. Thus, changing it now would not be a major breaking change.

Besides, rustc doesn't even have enough information to figure out whether the comparison would be cheap or not

There's a pretty simple heuristic that'd cover many cases including this one: numeric primitives and references to them can be assumed to be cheaper to check than any other type. It's an optimization - it doesn't need to be perfect as long as it won't make things worse.

@moxian
Copy link
Contributor

moxian commented May 20, 2025

The current documentation for derive(PartialEq) does not state the order in which the comparisons occur, unlike derive(PartialOrd).

You are right, i managed to grossly misread the OP. My apologies.

@lolbinarycat lolbinarycat added I-slow Issue: Problems and improvements with respect to performance of generated code. A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such and removed C-discussion Category: Discussion or questions that doesn't represent real issues. needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels May 27, 2025
@Sol-Ell
Copy link
Contributor Author

Sol-Ell commented May 28, 2025

I'm willing to take on this if no one else is working on it.

bors added a commit that referenced this issue Jun 3, 2025
Rollup of 8 pull requests

Successful merges:

 - #141724 (fix(#141141): When expanding `PartialEq`, check equality of scalar types first.)
 - #141833 (`tests/ui`: A New Order [2/N])
 - #141861 (Switch `x86_64-msvc-{1,2}` back to Windows Server 2025 images)
 - #141914 (redesign stage 0 std follow-ups)
 - #141918 (Deconstruct values in the THIR visitor)
 - #141923 (Update books)
 - #141931 (Deconstruct values in the THIR visitor)
 - #141956 (Remove two trait methods from cg_ssa)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors closed this as completed in 1943766 Jun 3, 2025
rust-timer added a commit that referenced this issue Jun 3, 2025
Rollup merge of #141724 - Sol-Ell:issue-141141-fix, r=nnethercote

fix(#141141): When expanding `PartialEq`, check equality of scalar types first.

Fixes #141141.

Now, `cs_eq` function of `partial_eq.rs` compares [scalar types](https://doc.rust-lang.org/rust-by-example/primitives.html#scalar-types) first.

- Add `is_scalar` field to `FieldInfo`.
- Add `is_scalar` method to `TyKind`.
- Pass `FieldInfo` via `CsFold::Combine` and refactor code relying on it.
- Implement `TryFrom<&str>` and `TryFrom<Symbol>` for FloatTy.
- Implement `TryFrom<&str>` and `TryFrom<Symbol>` for IntTy.
- Implement `TryFrom<&str>` and `TryFrom<Symbol>` for UintTy.
lnicola pushed a commit to lnicola/rust-analyzer that referenced this issue Jun 9, 2025
Rollup of 8 pull requests

Successful merges:

 - rust-lang/rust#141724 (fix(rust-lang/rust#141141): When expanding `PartialEq`, check equality of scalar types first.)
 - rust-lang/rust#141833 (`tests/ui`: A New Order [2/N])
 - rust-lang/rust#141861 (Switch `x86_64-msvc-{1,2}` back to Windows Server 2025 images)
 - rust-lang/rust#141914 (redesign stage 0 std follow-ups)
 - rust-lang/rust#141918 (Deconstruct values in the THIR visitor)
 - rust-lang/rust#141923 (Update books)
 - rust-lang/rust#141931 (Deconstruct values in the THIR visitor)
 - rust-lang/rust#141956 (Remove two trait methods from cg_ssa)

r? `@ghost`
`@rustbot` modify labels: rollup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) C-optimization Category: An issue highlighting optimization opportunities or PRs implementing such I-slow Issue: Problems and improvements with respect to performance of generated code. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants