Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

perf: use box instead of vec, write_all instead of loop #467

Closed
wants to merge 1 commit into from

Conversation

CosminPerRam
Copy link
Contributor

@CosminPerRam CosminPerRam commented Feb 23, 2025

In bufread:
header is used only for reading the data, so Boxing the slice instead of using a Vec is more memory efficient (16 vs 24).

In write:
As we just write until we encounter an error or empty the data, we can use write_all to simplify things and just reset the header afterwards, but I'm not sure if this is ok to do so, we are draining the header as we write and halt if we encounter an error, whereas this PR does not drain the data 'progressively', only if writing as a whole is successful (additional question: is this considered to be a breaking change if we change this behaviour?).

Unfortunately I don't know enough about Gzip to conclude if this is ok or problematic, can someone clarify? While looking through the code I do have found this exact behaviour in other places and wanted to make perf PRs, but tests pass successfully and I'm not sure if these might pose problems eventually.

@oyvindln
Copy link
Contributor

oyvindln commented Feb 23, 2025

The second change would require the first call to write to have at least 8 bytes in the buffer or fail while it could previously succeed with less by only writing part of the header and continuing in a later call with more output buffer available so I don't know if the change would be okay. It might not be something that is tested for currently and probably not something that comes up very often but it it is something that could happen I guess. It's not really anything specific to gzip. Is there some substantial overhead from how it's implemented currently?

As a side note I think you could further reduce the write struct site slightly by using a smaller data type for crc_bytes_written, it seems to only be used for keeping track of how many of the 8 bytes of the crc trailer was written so it doesn't need to store very large values.

Copy link
Member

@Byron Byron left a comment

Choose a reason for hiding this comment

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

Thanks a lot for contributing, it's wonderful to see this codebase getting an in-depth review and the improvements coming out of that.

I think it's better to make this PR about reducing the size of the GzEncoder struct, there should be no objections to merging it when done.

Thanks again

Comment on lines -131 to -134
while !self.header.is_empty() {
let n = self.inner.get_mut().write(&self.header)?;
self.header.drain(..n);
}
Copy link
Member

Choose a reason for hiding this comment

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

This change we should probably avoid for safety - in practice I'd expect the entire header to written anyway, but as a corner case, this fine-grained write handling is certainly preferable.

Copy link
Contributor

Choose a reason for hiding this comment

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

write_all makes sense here. It is essentially the same as the loop. However the drain or reassignment of self.header is not necessary. We can just leave that for the final deallocation of the encoder.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for sharing!
I think the original concern comes from what happens if the write fails. Semantically, the current version would be able to continue exactly where it left off, when seen just by itself. I don't know if that is anything to be concerned about though, or if it's anything that would happen in practice.

Since you suggested to do the header draining/clearing on drop essentially, you don't seem to share the concern so maybe it's save to just go with the most obvious implementation?

But if so, why would someone write the current version in the first place, if not with the intend that it is granular.

Copy link
Contributor

Choose a reason for hiding this comment

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

So it appears that the code is written this way to allow a write to fail with an error, but the caller can call write again and, if the error does not re-occur, then the file gets written correctly.

That might happen for non-blocking writes where EAGAIN gets returned. write_all isn't so useful in this case since it loses how many bytes were written before the EAGAIN (or an EINTR).

I agree then that it is safer to keep this as it is.

@@ -57,7 +57,7 @@ pub fn gz_encoder<R: BufRead>(header: Vec<u8>, r: R, lvl: Compression) -> GzEnco
let crc = CrcReader::new(r);
GzEncoder {
inner: deflate::bufread::DeflateEncoder::new(crc, lvl),
header,
header: header.into_boxed_slice(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally I expect users to be more concerned by performance than storage size for these types. Converting a Vec into a boxed slice potentially reallocates, and I suspect the possible performance loss is not really worth the memory saving.

Copy link
Member

Choose a reason for hiding this comment

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

Right! It's probably the shedding of excess capacity that can cause the reallocation.

Let's keep the Vec then.

Copy link
Member

@Byron Byron left a comment

Choose a reason for hiding this comment

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

Then I think the PR can be closed as all proposed changes have had to be rejected.

Thanks again for making proposing them, I hope you keep looking for more opportunities to improve the codebase. Your work is much appreciated.

@CosminPerRam
Copy link
Contributor Author

Thanks everyone for the feedback regarding these changes, will close this as the proposed changes aren't right/needed.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants