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

Support custom key type #69

Merged
merged 5 commits into from
Nov 23, 2024

Conversation

jakoschiko
Copy link
Contributor

@jakoschiko jakoschiko commented Nov 1, 2024

This PR is an alternative to:

This PR is a combination of both PRs:

  • It supports other primitive integers
  • It supports user provided types as long as the type is based on a primitive integer
  • It requires a breaking change
  • It does not require any additional dependencies

To achieve that two new traits are introduced. The first trait is a sealed trait for primitive integers:

pub trait Int: SealedInt {}

It defines the properties of an integer. It's implemented for u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128 and isize.

The second trait is an open trait for user provided keys:

pub trait IntKey: Copy {
    type Int: Int;
    fn into_int(self) -> Self::Int;
}

It defines how the key can be mapped to an integer. It's implemented for:

  • u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128 and isize
  • std::num::NonZeroU8, std::num::NonZeroU16, ...
  • std::num::Saturating I removed it because it's not supported by the current MSRV
  • std::num::Wrapping
  • std::net::Ipv4Addr
  • std::net::Ipv6Addr

And it can be implemented by the user.

IntMap has now a type parameter K:

pub struct IntMap<K, V> {
    // ...
}

impl<K: IntKey, V> IntMap<K, V> {
    // ...
}

Other changes worth mentioning:

  • I changed the prime number for u64, see Not the largest prime? #68
  • Some iterators had unnecessary lifetime bounds on K or V, e.g. V: 'a
  • Some benchmarks used &u64 as key instead of u64 because of type inference, I made it consistent

Closes #61
Closes #68

@jakoschiko
Copy link
Contributor Author

IntKey could also provide the prime number:

pub trait IntKey: Copy {
    type Int: Int;
    const PRIME: Self::Int;
    fn into_int(self) -> Self::Int;
}

@jakoschiko
Copy link
Contributor Author

Benchmarks before:

basic_bench                              fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ u64_get_intmap                        12.31 µs      │ 35.8 µs       │ 13.1 µs       │ 13.81 µs      │ 100     │ 100
├─ u64_insert_intmap                     28.6 µs       │ 160 µs        │ 30.17 µs      │ 32.15 µs      │ 100     │ 100
├─ u64_insert_intmap_checked             29.36 µs      │ 139 µs        │ 32.8 µs       │ 34.18 µs      │ 100     │ 100
├─ u64_insert_intmap_entry               53.7 µs       │ 153.5 µs      │ 60.21 µs      │ 61.21 µs      │ 100     │ 100
├─ u64_insert_without_capacity_intmap    480.1 µs      │ 614.6 µs      │ 489.3 µs      │ 507.2 µs      │ 100     │ 100
╰─ u64_resize_intmap                     22.28 µs      │ 29.86 µs      │ 22.32 µs      │ 22.55 µs      │ 100     │ 100

Benchmarks after:

basic_bench                              fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ u64_get_intmap                        11.61 µs      │ 36.21 µs      │ 11.68 µs      │ 12.42 µs      │ 100     │ 100
├─ u64_insert_intmap                     27.5 µs       │ 134.1 µs      │ 28.1 µs       │ 29.65 µs      │ 100     │ 100
├─ u64_insert_intmap_checked             35.06 µs      │ 143.9 µs      │ 41.44 µs      │ 42.34 µs      │ 100     │ 100
├─ u64_insert_intmap_entry               44.57 µs      │ 149.9 µs      │ 47.27 µs      │ 48.89 µs      │ 100     │ 100
├─ u64_insert_without_capacity_intmap    472.6 µs      │ 520.3 µs      │ 478.9 µs      │ 481.3 µs      │ 100     │ 100
╰─ u64_resize_intmap                     20.82 µs      │ 24.84 µs      │ 20.88 µs      │ 21.01 µs      │ 100     │ 100

I also added some new benchmarks for comparing different keys:

key_comparison              fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ get                                    │               │               │               │         │
│  ├─ u128                  20 µs         │ 40.59 µs      │ 20.17 µs      │ 20.84 µs      │ 100     │ 100
│  ├─ u16                   12.77 µs      │ 37.23 µs      │ 12.85 µs      │ 13.89 µs      │ 100     │ 100
│  ├─ u32                   13.19 µs      │ 35.06 µs      │ 13.59 µs      │ 14.16 µs      │ 100     │ 100
│  ╰─ u64                   12.71 µs      │ 36.19 µs      │ 13.53 µs      │ 14.19 µs      │ 100     │ 100
├─ insert                                 │               │               │               │         │
│  ├─ u128                  42.5 µs       │ 149.8 µs      │ 43.57 µs      │ 45.33 µs      │ 100     │ 100
│  ├─ u16                   34.1 µs       │ 141.7 µs      │ 35.19 µs      │ 37.71 µs      │ 100     │ 100
│  ├─ u32                   30.38 µs      │ 141.3 µs      │ 31.01 µs      │ 32.82 µs      │ 100     │ 100
│  ╰─ u64                   33.68 µs      │ 156.5 µs      │ 34.37 µs      │ 38.06 µs      │ 100     │ 100
╰─ insert_without_capacity                │               │               │               │         │
   ├─ u128                  508.8 µs      │ 590.1 µs      │ 520.5 µs      │ 522.3 µs      │ 100     │ 100
   ├─ u16                   483.9 µs      │ 617.7 µs      │ 489.2 µs      │ 492.3 µs      │ 100     │ 100
   ├─ u32                   487.8 µs      │ 515.6 µs      │ 494.4 µs      │ 495.2 µs      │ 100     │ 100
   ╰─ u64                   504.8 µs      │ 532.8 µs      │ 508.9 µs      │ 510.1 µs      │ 100     │ 100

@jakoschiko jakoschiko force-pushed the custom-key-type branch 3 times, most recently from 4a73a7b to 9a070d1 Compare November 1, 2024 00:48
@jakoschiko jakoschiko changed the title Allow custom key type Support custom key type Nov 1, 2024
@JesperAxelsson
Copy link
Owner

I like this one the best so far. Adding direct support for ip addresses were a nice touch :)

You have a few let int = key.into_int();, it might be worth considering shadowing key instead. As far as get or insert is concerned it's a key. Just a small nitpick.

I'll wait a few days to let @414owen come with feedback, then I'll merge and create a new major release.

Otherwise, great job and thank you for your patience.

@jakoschiko
Copy link
Contributor Author

Adding direct support for ip addresses were a nice touch :)

:p

You have a few let int = key.into_int();, it might be worth considering shadowing key instead. As far as get or insert is concerned it's a key. Just a small nitpick.

This does not work in all cases because the actual key is used afterwards. I renamed int to k, a shorter name for a variable with a smaller scope.

I'll wait a few days to let @414owen come with feedback, then I'll merge and create a new major release.

Sounds good. If you don't mind, I'd also want to complete #56 and #70 before the next major release.

What do you think about adding const PRIME: Self::Int; to the trait IntKey? Then a user could choose another prime number by using a wrapper for the key.

@JesperAxelsson
Copy link
Owner

JesperAxelsson commented Nov 7, 2024

Sounds good. If you don't mind, I'd also want to complete #56 and #70 before the next major release.

Sure, I don't mind waiting a bit.

What do you think about adding const PRIME: Self::Int; to the trait IntKey? Then a user could choose another prime number by using a wrapper for the key.

Yeah, that might be useful if someone want to use 256 bit or 7 bit sized key for some reason.

@jakoschiko jakoschiko mentioned this pull request Nov 7, 2024
@jakoschiko
Copy link
Contributor Author

Int represents a primitive integer and the user can't implement it. 256 bit is not possible because there is no primitive integer with 256 bit. But I would assume that for such types HashMap is better anyway.

@jakoschiko
Copy link
Contributor Author

I made some changes:

  • Int is now implemented only for unsigned integers. That makes it easier to support custom prime numbers. Note that signed integers can still be used as key because they implement IntKey.
  • I added support for custom prime numbers via IntKey::Prime.
  • I improved the doc. IntKey has now an example that shows how it can be implemented.
  • I removed unnecessary trait bounds for Int. This is just an internal cleanup.

@jakoschiko
Copy link
Contributor Author

Should I also increase the version number in this PR to indicate the breaking change?

README.md Outdated Show resolved Hide resolved
Co-authored-by: Damian Poddebniak <[email protected]>
This was referenced Nov 23, 2024
@JesperAxelsson JesperAxelsson merged commit 40f9232 into JesperAxelsson:master Nov 23, 2024
2 checks passed
@jakoschiko jakoschiko deleted the custom-key-type branch November 23, 2024 17:02
@jakoschiko jakoschiko restored the custom-key-type branch November 23, 2024 17:02
@jakoschiko jakoschiko deleted the custom-key-type branch December 13, 2024 10:59
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.

Not the largest prime? Support more integer types
3 participants