Skip to content

Dedupe vm constants - #726

Closed
rben01 wants to merge 10 commits into
sharkdp:masterfrom
rben01:dedupe-vm-constants
Closed

Dedupe vm constants#726
rben01 wants to merge 10 commits into
sharkdp:masterfrom
rben01:dedupe-vm-constants

Conversation

@rben01

@rben01 rben01 commented May 15, 2025

Copy link
Copy Markdown
Contributor

Builds on #712 by making Constant: Hash so that we can stick it in an IndexSet. Therefore all the deduplication logic can rest solely on the nature of an IndexSet.

This required quite a few changes. Most notably, Number is now opaque instead of providing access to its contained f64 via .0 because it no longer contains an f64, but rather a u64, which is converted to and from an f64 with f6::to_bits/f64::from_bits. But also, we have a new Dummy variant in Constant that stores a distinct value each time it's created. (This is of course necessary for the deduplication logic to work correctly; identical dummies would occupy the same slot in the map.)

triallax and others added 9 commits April 7, 2025 20:22
(except when defining derived unit constants, as initially identical but
temporary values)

Don't see any real performance changes from this (if anything it's
probably a minor slowdown due to the added dupe check in
`add_constant`). However, it does reduce the number of constants
generated from the prelude from ~730 to ~320, a reduction of 56%. This
in theory allows for larger Numbat programs as it's now harder to hit
the ~65k constants ceiling. Worth it? I don't know :)
…truction, instead *requiring* `new_dummy` to construct a Dummy
@sharkdp

sharkdp commented Jun 6, 2025

Copy link
Copy Markdown
Owner

Thank you for working on this.

Before I review this, I'd like to understand the core motivation better. Is there any upsides to this beside what's mentioned in the PR description at #712 (comment)?

@rben01

rben01 commented Jun 6, 2025

Copy link
Copy Markdown
Contributor Author

I believe this implementation allows for constant-time lookup of stored values, whereas the referenced implementation uses linear lookup (calls to .iter().position())

@sharkdp

sharkdp commented Jun 6, 2025

Copy link
Copy Markdown
Owner

This required quite a few changes. Most notably, Number is now opaque instead of providing access to its contained f64 via .0 because it no longer contains an f64, but rather a u64, which is converted to and from an f64 with f6::to_bits/f64::from_bits

I'm skeptical about this part. This won't work if we want to replace our number representation, right?

@Goju-Ryu

Goju-Ryu commented Jun 6, 2025

Copy link
Copy Markdown
Contributor

I'm skeptical about this part. This won't work if we want to replace our number representation, right?

I was thinking this could be a step in that direction. Disentangling the underlying representation by making it opaque. I might be missing some consequences though.

@sharkdp

sharkdp commented Jun 6, 2025

Copy link
Copy Markdown
Owner

Making it opaque is certainly a good idea, but it sounded like we use the bits of the f64 for the integer? How would that work for some more complex heap-allocated precision datatype?

@rben01

rben01 commented Jun 8, 2025

Copy link
Copy Markdown
Contributor Author

I'm skeptical about this part. This won't work if we want to replace our number representation, right?

The u64’s only purpose is to be Eq and Hash; any underlying representation that implements those two traits would work just as well. Of course, if we think we'll want to eventually swap in a representation that can't be made Eq or Hash, then we shouldn't implement it here either, and just accept that Number can never go in a Map or Set.

Making it opaque is certainly a good idea, but it sounded like we use the bits of the f64 for the integer? How would that work for some more complex heap-allocated precision datatype?

I believe the only methods that would have to be updated are Number::from_f64 and Number::to_f64, to take into account the new underlying representation. Of course, if we were to use a more capable representation under the hood, then we'd probably want more powerful methods (e.g. a way to get a number more precise than an f64 out), but we wouldn't need them.

Edit: oops, this representation breaks k1 == k2 -> hash(k1) == hash(k2), as -0.0 == +0.0 but they will (probably) hash differently. I assume this is fixable by simply checking if the value is == 0.0 and if it is, return the hash of +0.0.

@sharkdp

sharkdp commented Oct 12, 2025

Copy link
Copy Markdown
Owner

Sorry for the late reply here. I'm still skeptical about the added complexity and potentially decreased flexibility w.r.t. the number format, especially since the benefits don't seem immediately clear (there are no benchmarks that would show better performance, for example). To be honest, I currently also don't have a lot of time to review this, so I'd like to close this for now. If you (or anyone else) thinks that this is a mistake, please feel free to comment here, and we can reconsider.

@sharkdp sharkdp closed this Oct 12, 2025
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.

4 participants