Skip to content

Conversation

@febo
Copy link
Collaborator

@febo febo commented Sep 10, 2025

Problem

In some cases, comparing pubkeys with the equality operator (==) results in "bloated" code, which eventually leads to an increased CU consumption.

Solution

This PR adds a pubkey_eq helper, which compares pubkeys as a sequence of u64 values.

Copy link
Contributor

@sonicfromnewyoke sonicfromnewyoke left a comment

Choose a reason for hiding this comment

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

what do you think about impl PartialEq for Pubkey in the sdk?
to have API like follows:

mint_a.key().eq(mint_b.key())

@febo
Copy link
Collaborator Author

febo commented Sep 10, 2025

what do you think about impl PartialEq for Pubkey in the sdk? to have API like follows:

mint_a.key().eq(mint_b.key())

Yeah, definitely – we should have it on the Address crate.

@febo febo marked this pull request as ready for review September 11, 2025 10:08
rustopian
rustopian previously approved these changes Sep 11, 2025
Copy link
Contributor

@rustopian rustopian left a comment

Choose a reason for hiding this comment

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

Looks good to me. One question on a use of unlikely.

Copy link
Contributor

@rustopian rustopian left a comment

Choose a reason for hiding this comment

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

Looks great.

@febo febo merged commit 1f63b1d into main Sep 11, 2025
11 checks passed
@febo febo deleted the febo/pubkey-eq branch September 11, 2025 23:18
Comment on lines +40 to +42
/// The implementation of this function is currently more efficient
/// than `p1 == p2` since it compares 8 bytes at a time instead of
/// byte-by-byte.
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is just a more efficient implementation of ==, it seems better to redefine PartialEq:

// Comparing `Pubkey`s 8 bytes at a time is faster, than comparing byte at a
// time.  One would expect LLVM to do this optimization for us, but it does not
// at the moment (as of platform tools v1.43).  Comparing keys is a pretty
// common operation, so we want to make it faster if we can.
impl PartialEq for Pubkey {
    fn eq(&self, other: &Self) -> bool {
        let p1 = self.as_ptr() as *const u64;
        let p2 = other.as_ptr() as *const u64;

        // SAFETY: `p1` and `p2` both point to `[u8; 32]` bytes of data, which
        // is the same size as `[u64; 4]`.
        unsafe {
            read_unaligned(p1) == read_unaligned(p2)
                && read_unaligned(p1.add(1)) == read_unaligned(p2.add(1))
                && read_unaligned(p1.add(2)) == read_unaligned(p2.add(2))
                && read_unaligned(p1.add(3)) == read_unaligned(p2.add(3))
        }
    }
}

Bikeshedding

Maybe it is possible to get the same effect if you just help the compiler a bit,
by casting [u8; 32] into [u64; 4]?

// Comparing `Pubkey`s 8 bytes at a time is faster than comparing byte at a
// time.  One would expect LLVM to do this optimization for us, but it does not
// at the moment (as of platform tools v1.43).  Comparing keys is a pretty
// common operation, so we want to make it faster if we can.
impl PartialEq for Pubkey {
    fn eq(&self, other: &Self) -> bool {
        let p1 = self.as_ptr() as *const [u64; 4];
        let p2 = other.as_ptr() as *const [u64; 4];

        // SAFETY: `p1` and `p2` both point to `[u8; 32]` bytes of data, which
        // is the same size as `[u64; 4]`.
        unsafe { read_unaligned(p1) == read_unaligned(p2) }
    }
}

I would also add static_assertions, to be able to encode the SAFETEY comment
in code:

use static_assertions::assert_eq_size;

// Comparing `Pubkey`s 8 bytes at a time is faster than comparing byte at a
// time.  One would expect LLVM to do this optimization for us, but it does not
// at the moment (as of platform tools v1.43).  Comparing keys is a pretty
// common operation, so we want to make it faster if we can.
impl PartialEq for Pubkey {
    fn eq(&self, other: &Self) -> bool {
        let p1 = self.as_ptr() as *const [u64; 4];
        let p2 = other.as_ptr() as *const [u64; 4];

        assert_eq_size!(*self, [u64; 4]);
        assert_eq_size!(*other, [u64; 4]);
        // SAFETY: `self` and `[u64; 4]` are of the same size.
        unsafe { read_unaligned(p1) == read_unaligned(p2) }
    }
}

(It is a compile time assertion with no runtime code.)

Copy link
Contributor

@rustopian rustopian Sep 15, 2025

Choose a reason for hiding this comment

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

I wanted this, too – at first. It would be great to get the savings everywhere devs use ==.

But since our pinocchio::pubkey::Pubkey is just an alias for [u8;32], we can't impl PartialEq on it. Creating a new type is maybe not worth it and probably a separate discussion.

Also, sounds like Lucas is looking into this, so maybe this discussion will be irrelevant soon.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If this is just a more efficient implementation of ==, it seems better to redefine PartialEq:

We have done this for the solana-address here, since Address is a type.

Bikeshedding

Maybe it is possible to get the same effect if you just help the compiler a bit, by casting [u8; 32] into [u64; 4]?

Unfortunately no, it is even worse. 😅 I think the compiler tries to do something different when it sees an array, which in the end becomes "bloated" and uses more CU. You can use this simple bench to see the effect.

As @rustopian mentioned, Lucas will have a look at this to see if we can improve the code generation so the compiler does the "right" thing.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wanted this, too – at first. It would be great to get the savings everywhere devs use ==.

But since our pinocchio::pubkey::Pubkey is just an alias for [u8;32], we can't impl PartialEq on it. Creating a new type is maybe not worth it and probably a separate discussion.

This is out of scope for this PR obviously, but, considering Pubkey is such a fundamental abstraction, I think it should be a type.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It will be very soon: #118 😊

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.

6 participants