-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
ffi::CString::new(string-literal) is suboptimal #35838
Comments
Note, there might be a second bug hidden in here, where the compiler should probably be able to find out that the realloc(ptr, 28) is useless and that it could realloc(ptr, 15) directly. |
I don't think we can make it optimal. We can't rely on |
A macro or syntax extension could generate unsafe code to conjure a |
CString::new uses |
We can fix the second reallocation by using |
cstring: avoid excessive growth just to 0-terminate Based on following what happens in CString::new("string literal"): 1. Using `Into<Vec<u8>>`, a Vec is allocated with capacity exactly equal to the string's input length. 2. By `v.push(0)`, the Vec is grown to twice capacity, since it was full. 3. By `v.into_boxed_slice()`, the Vec capacity is shrunk to fit the length again. If we use `.reserve_exact(1)` just before the push, then we avoid the capacity doubling that we're going to have to shrink anyway. Growing by just 1 byte means that the step (2) is less likely to have to move the memory to a larger allocation chunk, and that the step (3) does not have to reallocate. Addresses part of rust-lang#35838
cstring: avoid excessive growth just to 0-terminate Based on following what happens in CString::new("string literal"): 1. Using `Into<Vec<u8>>`, a Vec is allocated with capacity exactly equal to the string's input length. 2. By `v.push(0)`, the Vec is grown to twice capacity, since it was full. 3. By `v.into_boxed_slice()`, the Vec capacity is shrunk to fit the length again. If we use `.reserve_exact(1)` just before the push, then we avoid the capacity doubling that we're going to have to shrink anyway. Growing by just 1 byte means that the step (2) is less likely to have to move the memory to a larger allocation chunk, and that the step (3) does not have to reallocate. Addresses part of #35838
I'm seeing the below output from ltrace. It looks like this is mostly fixed, since I don't think there's really anything we can do to not realloc the CString into 15 bytes. Please reopen if that's the wrong interpretation, though.
|
The Vec could be initialized to its final size, which is known in advance (length of the &'static str + 1) |
I'm fairly certain that there's little to nothing we can do about it, just an LLVM optimization. |
We should be able to use specialization, as mentioned #35871 (comment) to remove the realloc for |
Actually, why isn't it optimized? I have a Rust crate wrapping a C library API and there's a lot of places where I need to pass over a string. Naturally, on the Rust side I accept a Currently, though, even this simplest example compiles into a pile of allocations and runtime checks, even with LTO and codegen-units 1: use std::ffi::CString;
#[inline(never)]
fn f(c_str: *const i8) {
println!("{:?}", c_str);
}
fn main() {
let c_string = CString::new("Hello there").unwrap();
f(c_string.as_ptr());
} Actually, building this as a simple bin crate locally and checking the disassembly, |
I believe we now specialize down to the best we can (given what we know) after #65551 so closing. |
Looking at the disassembly on 1.40.0, creating a CString from a string literal still has an allocation, a memchr and a |
I think most of the checks could likely be eliminated with some inlining, although I would personally not be sure we should be inlining these functions -- there's not too much benefit. The allocation though couldn't be avoided, though (CString must be owned). |
@Mark-Simulacrum I think, nothing more can be achieved here so probably we should just close it. |
I noticed this while doing some ltrace using alloc_system on my panicking program. A small test case is
What I noticed is the following pattern:
(not in ltrace, but likely: ptr[15] = '\0')
If realloc is not in-place, that's 3 copies of the string, and 3 allocations.
This all stems from the use of
env::var_os("RUST_BACKTRACE")
inlibrustc_driver/lib.rs
, which ends up doingffi::CString::new("RUST_BACKTRACE")
(and indeed replacing panic!() in the test case above with thatffi::CString::new
call yields the same copy/alloc pattern.Ironically, the "RUST_BACKTRACE" static string which is copied the first time, is already null terminated in the executable binary's rodata section.
The text was updated successfully, but these errors were encountered: