-
Notifications
You must be signed in to change notification settings - Fork 13.3k
BufWriter remove Option #59759
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
BufWriter remove Option #59759
Conversation
r? @dtolnay (rust_highfive has picked a reviewer for you, use r? to override) |
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
Current bench results: With option
Without option
|
The job Click to expand the log.
I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact |
use crate::mem::forget; | ||
use crate::ptr::{drop_in_place, read}; | ||
let inner = unsafe { read(&mut self.inner) }; | ||
unsafe { drop_in_place(&mut self.buf) }; |
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.
I notice that if you have a global memory allocator that can panic in dealloc, a panic here would turn into a double-free of inner
and self.inner
. The GlobalAlloc trait is currently documented as:
- It's undefined behavior if global allocators unwind. This restriction may be lifted in the future, but currently a panic from any of these functions may lead to memory unsafety.
so this seems fine for now but needs at least a comment why the code is safe despite appearances.
Ok(()) => { | ||
use crate::mem::forget; | ||
use crate::ptr::{drop_in_place, read}; | ||
let inner = unsafe { read(&mut self.inner) }; |
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.
This approach of ~manually running the drop glue for all of the other parts of the struct seems a bit dangerous and error prone. Using ManuallyDrop::take
seems like it'd be cleaner: https://doc.rust-lang.org/std/mem/struct.ManuallyDrop.html?search=#method.take
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.
What are you suggesting applying ManuallyDrop::take
in this context?
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.
I think you would need the struct to contain:
inner: ManuallyDrop<W>,
buf: ManuallyDrop<Vec<u8>>,
where you call ManuallyDrop::drop(&mut self.buf); ManuallyDrop::drop(&mut self.inner)
inside Drop and ManuallyDrop::drop(&mut self.buf); ManuallyDrop::take(&mut self.inner)
inside into_inner.
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.
I don't like that implementation at all. The entire point of this change is to isolate this edge case into one function rather than spreading it throughout the entire struct.
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.
Perhaps he meant putting self
into a ManuallyDrop
to prevent these double free issues? That seems reasonable:
use crate::mem::ManuallyDrop;
use crate::ptr::{drop_in_place, read};
let mut s = ManuallyDrop::new(self);
let inner = unsafe { read(&mut s.inner) };
unsafe { drop_in_place(&mut s.buf) };
Ok(inner)
I don't think that the 1-2% difference in microbenchmark (when measuring with io::sink, not even doing real I/O) is worth the added complexity of this change. What do you think @czipperz? Were you counting on a greater improvement? |
I think micro optimizations such as this are generally good so long as they don't break correctness. This is a complex problem and must have a complex solution. The existing solution is to special case |
New micro-optimizations impose an increased maintenance burden, correctness risk, and even soundness risk when they contain unsafe code. Because of that, I prefer to limit them to solve problems that actually exist. Has anyone actually traced a concrete performance issue to the use of Option in BufReader? |
I used the word generally because I agree, there definitely are optimizations that abuse unsafe code and add complexity. However, in this specific case, I don't believe this is the case. Suppose that the current implementation didn't have the If I am reading through |
I don't think you've answered my question. |
I highly doubt it. And I agree, making a change to unsafe code to purely increase performance is bad. Especially when there is such a small gain. However, I also think the added complexity of smearing the |
The There are reasons to add unsafe code to the standard library. A reader of the source code being confused for a couple minutes over why there's an |
Just to be clear, I'm coming to rust from c/c++ where manual memory management like this is normalized. So I'm not very scared of it personally. |
Irregardless of whether it is common or not, I would still argue it is more complex than needs be. I also think that the |
Rust exists because the past 30 years have demonstrated that human beings are not capable of writing large, complex codebases in C and C++ that manage memory correctly. |
I agree. Its one of the reasons I moved to rust. Thanks for the feedback @sfackler . |
Closes #36319
See #36336 for previous discussion