-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
Pad shreds on retrieval from blockstore only when needed #17204
Conversation
There are several different "stages" for a data shred:
@sakridge @behzadnouri (and anyone else with thoughts on the matter) - I'd be interested to get some opinions on this. It seemed like a good idea when Carl and I discussed, but after having implemented it, I'm semi-jaded as it seems kind of error-prone to keep track of these stages. I could finish things off and have it working well, but seems a little brittle. The other option which I pitched over in https://github.com/solana-labs/solana/pull/17175/files would be to always add the zero-padding back to a shred when it gets pulled from the blockstore. However, this wasn't the cleanest solution either. |
4a44404
to
303827d
Compare
Codecov Report
@@ Coverage Diff @@
## master #17204 +/- ##
=========================================
- Coverage 82.6% 82.6% -0.1%
=========================================
Files 425 425
Lines 118842 118889 +47
=========================================
+ Hits 98271 98306 +35
- Misses 20571 20583 +12 |
@steviez asked me to take a look at this pr. My thoughts (mostly copy paste from slack chat):
So my suggestion is to avoid this unless there is a demonstrable performance gain. If there is such a performance gain, then probably we can at least add some type safety. Something like changing the shred data type to:
and then explicitly track where things are padded vs trimmed. It will require some work around implementing serialization and deserialization for backward compatibility but it is much better than the situation that if a function obtains a shred, we do not know if its data is padded or not. Even with the addition of above type safety thing, this require some work and diligence, so might be better to hold off until there is a performance benchmark justifying the effort. |
@carllin - Per our conversation in DM, I threw together a quick benchmark to try to actually understand what kind of performance bump we're looking at. I pushed it into this branch here Here is the output if you run that (these numbers are from my MacBook Pro):
With the built in bencher, anything that goes inside So, Notes:
So, allocating the vector takes ~113 ns, allocating and resize takes ~272 ns, so we can surmise that the resize is taking ~159 ns, or less than 0.2 us. A 0.2 us savings that we only get in a few cases (erasure / broadcast require the resize) seems pretty minimal, what do you think ? |
I think one way to prevent that is to have the lambda that you are using in
yeah, does not seem significant to me either; at least considering the code complexity needed to avoid it. I do not expect that this is the bottleneck on any code path. |
fe631ab
to
1108075
Compare
Hey, was just about to post an update but you beat me to it @behzadnouri 😄. @carllin and I dove into this a little bit yesterday. For starters, we replicated similar numbers on a validator, so the tests on my local machine are valid (same orders of magnitude). That being said, Carl did observe that on a validator under load, the number Next, we realized that it probably makes more sense to think about this number as it relates to deserialization. Also, the bench I threw up yesterday was a bit convoluted, so I added a duplicate deserialize function that does a resize; this allows us to better compare the cost of the resize in relation to the cost of deserialize. That can be found here
The top one skips the resize; the bottom one does the resize. So, this shows us the resize is costing about 100 ns = 0.1 us, and that the resize results in about 40% increase in deserialize time. Carl mentioned that he thought we may get up to around 5000 shreds / slot at the moment. Using that number: So, 0.125% of slot duration. If we bumped up to 10,000 shreds / slot and 200 ms slots, that'd get us to 1 ms/slot and 0.5% of slot duration. |
fcc137b
to
e93e602
Compare
97ffab1
to
34ee441
Compare
34ee441
to
d5cf747
Compare
fn get_padded_cf(&self, cf: &ColumnFamily, key: &[u8], size: usize) -> Result<Option<Vec<u8>>> { | ||
let opt = self.0.get_cf(cf, key)?.map(|db_vec| { | ||
let mut bytes = vec![0; size]; | ||
{ | ||
let (data, _padding) = bytes.split_at_mut(db_vec.len()); | ||
data.copy_from_slice(&db_vec[..]); | ||
} | ||
bytes | ||
}); | ||
Ok(opt) | ||
} |
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.
The reason to break this out into a separate function was because of get_cf()
's implementation:
fn get_cf(&self, cf: &ColumnFamily, key: &[u8]) -> Result<Option<Vec<u8>>> {
let opt = self.0.get_cf(cf, key)?.map(|db_vec| db_vec.to_vec());
Ok(opt)
}
Note the db_vec.to_vec()
; this clones db_vec into a new vector. If we do the resize later, then a second allocation & memcpy will occur with Vector::resize(); doing this here shaves that off.
@@ -247,6 +247,13 @@ impl Shred { | |||
packet.meta.size = len; | |||
} | |||
|
|||
pub fn copy_from_packet(packet: &Packet) -> Result<Self> { | |||
let mut serialized_shred = vec![0; SHRED_PAYLOAD_SIZE]; | |||
// TODO: assert packet.data.len() >= SHRED_PAYLOAD_SIZE / == PACKET_DATA_SIZE ? |
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.
Thoughts on this assertion? We had something like this in window service; we could move it here to avoid repeated lines for all the callers of this.
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
This stale pull request has been automatically closed. Thank you for your contributions. |
Problem
#16602 introduced a change where shreds would have zero-padding stripped upon insertion to the blockstore, and re-added upon retrieval. However, discussion here let us to find that the zero-padding wasn't consistently applied, depending on the method used to retrieve shred(s) (there are multiple methods to pull shreds out).
Summary of Changes
This PR introduces a change in philosophy @carllin and I discussed where shreds will intentionally NOT be zero-padded when pulled out of the blockstore unless explicitly needed. There are several instances where zero padding may be necessary:
In other cases, the idea is to not add the zero-padding back as we don't explicitly need it. This change in philosophy saves us some bytestream resizing; however, it comes at the cost of complexity of needing to know when the bytestream has had the zero-padding stripped vs. when it has not.
Fixes #