-
Notifications
You must be signed in to change notification settings - Fork 27
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
Use const generics to remove BitTree heap allocations #79
Conversation
02ecc33
to
37e09d1
Compare
This comment was marked as outdated.
This comment was marked as outdated.
0340dd6
to
9223b59
Compare
This comment was marked as outdated.
This comment was marked as outdated.
9223b59
to
64f9980
Compare
e211b5d
to
260eefe
Compare
Marking this as ready since #77 was merged. I've cleaned up the prior commits and just left the two possible APIs. @gendx you will want to review both 06aee69 and 260eefe which are identical except for the signature of Given that Unfortunately |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for the delay in the last few months.
Overall this PR looks good, I have some comments mostly regarding the static assertion macro.
Apart from that, I've just added code coverage reporting (#86), so let's see how this PR works with it.
e67c2bc
to
025c331
Compare
Codecov ReportBase: 86.83% // Head: 87.08% // Increases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## master #79 +/- ##
==========================================
+ Coverage 86.83% 87.08% +0.25%
==========================================
Files 19 19
Lines 2484 2540 +56
==========================================
+ Hits 2157 2212 +55
- Misses 327 328 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
The single-argument that BitTree takes is 1 << NUM_BITS (2 ** NUM_BITS) for the number of bits required in the tree. This is due to restrictions on const generic expressions. The validity of this argument is checked at compile-time with a macro that confirms that the argument P passed is indeed 1 << N for some N using usize::trailing_zeros to calculate floor(log_2(P)). Thus, BitTree<const P: usize> is only valid for any P such that P = 2 ** floor(log_2(P)), where P is the length of the probability array of the BitTree. This maintains the invariant that P = 1 << N.
025c331
to
571be60
Compare
I've rebased on master, fixing the conflicts and squashed the commits into one. Looks like there's a small improvement in code coverage as well! |
@gendx Any updates on getting this merged? |
Apologies again for the delay. I'll merge this now and fix any pending issue on top of it. Thanks a lot for your contribution! |
Pull Request Overview
#19 was blocked on
#[feature(generic_const_exprs)]
for the1 << NUM_BITS
issue but we do not actually needgeneric_const_exprs
to utilize const generics at the expense of having a slightly surprising API forBitTree
. SinceBitTree
is an internal implementation detail, I do not think it is too much of a trade-off for the performance benefits seen. The strong compile time guarantee of the validity ofBitTree
when usingNUM_BITS
in an expression can be replicated with a compile time assert in the constructor.There are 2 different API shapes forBitTree
proposed here. Both APIs validate their const arguments at compile time, and invalid instances ofBitTree
are still unconstructable.1.BitTree<const NUM_BITS: usize, const PROBS_ARRAY_LEN: usize>
(06aee69)2.
BitTree<const PROBS_ARRAY_LEN: usize>
(260eefe)The first signature must have the caller input both
NUM_BITS
, andPROBS_ARRAY_LEN == 1 << NUM_BITS
as part of the type signature. The equality ofPROBS_ARRAY_LEN
is checked by a compile-time assert macro that simply checks that indeedPROBS_ARRAY_LEN == 1 << NUM_BITS
. This prevents the construction of aBitTree
wherePROBS_ARRAY_LEN
is not1 << NUM_BITS
The second signature takes only
PROBS_ARRAY_LEN
to sidestep the requirement forgeneric_const_exprs
. Instead,NUM_BITS
is an associated constant that is calculated asfloor(log_2(PROBS_ARRAY_LEN))
usingPROBS_ARRAY_LEN.trailing_zeros()
which has beenconst
since Rust 1.32. The const assert instead checks thatPROBS_ARRAY_LEN == 1 << (PROBS_ARRAY_LEN.trailing_zeros())
. Thus, rather thanBitTree
being valid for anyNUM_BITS
andPROBS_ARRAY_LEN == 1 << NUM_BITS
,BitTree
in this API is valid for anyPROBS_ARRAY_LEN
such thatPROBS_ARRAY_LEN == 2 ** floor(log_2(PROBS_ARRAY_LEN))
, which gives us effectively the same guarantees as the first signature without the redundantNUM_BITS
argument.Both signatures compile to the same code and have the same performance. It is a matter of preference and readability on which to take. I have a slight preference for the second signature because it is less noisy and seeing
BitTree<{1 << 8}>
for example seems fairly obvious, but purely looking at the implementation, I prefer the less 'clever' first signature.This PR bumps the MSRV to 1.57 for const generics and const_panic.
Benchmarks
In general, variances are reduced and there is a speed bump when accessing the dictionary during decompression. While I did apply const-generics to
encode::rangecoder::BitTree
for consistency, they don't seem to be currently used for compression so compression benchmarks should not be heavily affected if at all.Without const generics
With const generic BitTree
This should fix #19, after this PR,
DecoderState
only has the single heap allocation ofliteral_probs
which is substantially more difficult to put on the stack.Testing Strategy