How to shrink a Vec or String from an offset without reallocating it? - rust

Multiple structures in rust have shrink_to or shrink_to_fit methods, such as Vec and String. But apparently there's nothing like shrink_from_to.
Why would I want that?
Assume I've a XY gigabyte string or vector in memory and know the exact start and end positions of the part I am interesting in (which allocates only Z GB from start to end, somewhere in the middle). I could call truncate and then shrink_from_to effectivly freeing memory.
However I've still gigabytes of memory occupied by [0..start] which are of no relevance for my further processing.
Question
Is there any way to free this memory too without reallocating and copying the relevant parts there?

Note that shrink_to_fit does reallocate by copying into a smaller buffer and freeing the old buffer. Your best bet is probably just converting the slice you care about into an owned Vec and then dropping the original Vec.
fn main() {
let v1 = (0..1000).collect::<Vec<_>>(); // needs to be dropped
println!("{}", v1.capacity()); // 1000
let v2 = v1[100..150].to_owned(); // don't drop this!
println!("{}", v2.capacity()); // 50
drop(v1);
}

Is there any way to free this memory too without reallocating and copying the relevant parts there?
Move the segment you want to keep to the start of the collection (e.g. replace_range, drain, copy_within, rotate, ...), then truncate, then shrink.
APIs like realloc and mremap work in terms of "memory blocks" (aka allocations returned by malloc/mmap), they don't work in terms of random pointers. So a hypothetical shrink_from_to would just be doing that under the cover, since you can't really resize allocations from both ends.

Related

How to overlay data structures?

I have an 8k buffer of bytes. The data in the first part of the buffer is highly structured, with a set of variables of size u32 and u16. The data in the later part of the buffer is for small blobs of bytes. After the structured data, there is an array of offsets that point to the start of each small blob. Something like this:
struct MyBuffer {
myvalue: u32,
myothervalue: u16,
offsets: [u16], // of unknown length
bytes: [u8] // fills the rest of the buffer
}
I'm looking for an efficient way to take an 8k blob of bytes fetched from disk, and then overlay it or cast it to the MyBuffer struct. In this way I can get/set the structured values easily (let myvar = my_buffer.myvalue), and I can also access the small blobs as slices (let myslice = my_buffer[offsets[2]..offsets[3]]).
The benefit of this approach is you get efficient, zero-copy access to the data.
The fact that the number of offsets and the number of blobs of bytes is unknown makes this tricky.
In C, it's easy; you just cast a pointer to the 8k buffer to the appropriate struct and it just works. You have two different data structures pointing at the same memory.
How can I do the same thing in Rust?
I have discovered that there is an entire Rust ecosystem dedicated to solving this problem. Rust itself handles it poorly.
There are many Rust serialization frameworks, including those that are "zero-copy". The best list is here: https://github.com/djkoloski/rust_serialization_benchmark
The zero-copy frameworks include abomonation, capnp, flatbuffers, rkyv, and alkahest.

Will data be copied byte by byte when moving a variable?

I'm new to rust, and I am wondering what exactly happens when moving a variable.
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 1, y: 1 };
let q = p;
}
When let q = p;, will the data (size of which is 8 bytes) be copied from a memory address to another? Since p is moved here thus it cannot be used anymore, I think it is good to make q's underlying memory address equal to p's. In another word, I think it is OK that nothing is copied in the machine code.
So my question is: will data be copied byte by byte when moving a variable? If it will, why?
[W]ill data be copied byte by byte when moving a variable?
In general, yes. To move a value, Rust simply performs a bitwise copy. If the value is not Copy, the source won't be used anymore after the move. If the value is Copy, both the source and the destination can be used.
However, there are many cases when the compiler backend can eliminate the copy by proving that the code beahves identical without the copy. This optimization happens completely in LLVM. In your example, the LLVM IR still contains the instructions to move the data, but the generated code does not contain the move even in debug mode.
If it will, why?
There are many reasons why the compiler can be unable to use the same memory for source and destination. In your example, with two variables in the same stack frame, it's easy to see that the move is not needed, but the code is a bit pointless anyway (though sometimes people do move values inside a function to make a variable immutable).
Here are just a few illustrations why the compiler may be unable to reuse the source memory for the destination:
The source value may be on the stack, while the destination is on the heap or vice versa. The statement let b = Box::new(3); will move the value 3 from the stack to the heap'; let i = *b; will move it from the heap back to the stack. It's still possible that the compiler can eliminate these moves, e.g. by writing the constant 3 to the heap immediately, without writing it to the stack first.
Source and destination may be on different stack frames, when moving values across functions – e.g. when passing a value into a function, or when returning a value from a function.
Source an destination values may be stored in struct fields, so they need to have the right offset inside the struct.
These are just a few examples. The takeaway is that in general, a move may result in a bitwise copy. Keep in mind that a bitwise copy is very cheap, though, and that the optimizer usually does a good job, so you should only worry about this if you actually have a proven performance bottleneck.

Rust pointer being freed was not allocated error

Here's the situation, I want to do some data conversion from a string, and for convenience, I converted it to a pointer in the middle, and now I want to return the part of the string, but I'm stuck with this exception:
foo(74363,0x10fd2fdc0) malloc: *** error for object 0x7ff65ff000d1: pointer being freed was not allocated
foo(74363,0x10fd2fdc0) malloc: *** set a breakpoint in malloc_error_break to debug
When I try to debug the program, I got the error message as shown above.
Here's my sample code:
fn main() {
unsafe {
let mut s = String::from_utf8_unchecked(vec![97, 98]);
let p = s.as_ptr();
let k = p.add(1);
String::from_raw_parts(k as *mut u8, 1, 1);
}
}
You should never use an unsafe function without understanding its documentation, 100%.
So, what does String::from_raw_parts says:
Safety
This is highly unsafe, due to the number of invariants that aren't
checked:
The memory at ptr needs to have been previously allocated by the same allocator the standard library uses, with a required alignment of exactly 1.
length needs to be less than or equal to capacity.
capacity needs to be the correct value.
Violating these may cause problems like corrupting the allocator's internal data structures.
The ownership of ptr is effectively transferred to the String which may then deallocate, reallocate or change the contents of memory pointed to by the pointer at will. Ensure that nothing else uses the pointer after calling this function.
There are two things that stand out here:
The memory at ptr needs to have been previously allocated.
capacity needs to be the correct value.
And those are related to how allocations work in Rust. Essentially, deallocation only expects the very pointer value (and type) that allocation returned.
Shenanigans such as trying to deallocate a pointer pointing in the middle of an allocation, with a different alignment, or with a different size, are Not Allowed.
Furthermore, you also missed:
Ensure that nothing else uses the pointer after calling this function.
Here, the original instance of String is still owning the allocation, and you are trying to deallocate one byte out of it. It cannot ever go well.

Does Iterator::collect allocate the same amount of memory as String::with_capacity?

In C++ when joining a bunch of strings (where each element's size is known roughly), it's common to pre-allocate memory to avoid multiple re-allocations and moves:
std::vector<std::string> words;
constexpr size_t APPROX_SIZE = 20;
std::string phrase;
phrase.reserve((words.size() + 5) * APPROX_SIZE); // <-- avoid multiple allocations
for (const auto &w : words)
phrase.append(w);
Similarly, I did this in Rust (this chunk needs the unicode-segmentation crate)
fn reverse(input: &str) -> String {
let mut result = String::with_capacity(input.len());
for gc in input.graphemes(true /*extended*/).rev() {
result.push_str(gc)
}
result
}
I was told that the idiomatic way of doing it is a single expression
fn reverse(input: &str) -> String {
input
.graphemes(true /*extended*/)
.rev()
.collect::<Vec<&str>>()
.concat()
}
While I really like it and want to use it, from a memory allocation point of view, would the former allocate less chunks than the latter?
I disassembled this with cargo rustc --release -- --emit asm -C "llvm-args=-x86-asm-syntax=intel" but it doesn't have source code interspersed, so I'm at a loss.
Your original code is fine and I do not recommend changing it.
The original version allocates once: inside String::with_capacity.
The second version allocates at least twice: first, it creates a Vec<&str> and grows it by pushing &strs onto it. Then, it counts the total size of all the &strs and creates a new String with the correct size. (The code for this is in the join_generic_copy method in str.rs.) This is bad for several reasons:
It allocates unnecessarily, obviously.
Grapheme clusters can be arbitrarily large, so the intermediate Vec can't be usefully sized in advance -- it just starts at size 1 and grows from there.
For typical strings, it allocates way more space than would actually be needed just to store the end result, because &str is usually 16 bytes in size while a UTF-8 grapheme cluster is typically much less than that.
It wastes time iterating over the intermediate Vec to get the final size where you could just take it from the original &str.
On top of all this, I wouldn't even consider this version idiomatic, because it collects into a temporary Vec in order to iterate over it, instead of just collecting the original iterator, as you had in an earlier version of your answer. This version fixes problem #3 and makes #4 irrelevant but doesn't satisfactorily address #2:
input.graphemes(true).rev().collect()
collect uses FromIterator for String, which will try to use the lower bound of the size_hint from the Iterator implementation for Graphemes. However, as I mentioned earlier, extended grapheme clusters can be arbitrarily long, so the lower bound can't be any greater than 1. Worse, &strs may be empty, so FromIterator<&str> for String doesn't know anything about the size of the result in bytes. This code just creates an empty String and calls push_str on it repeatedly.
Which, to be clear, is not bad! String has a growth strategy that guarantees amortized O(1) insertion, so if you have mostly tiny strings that won't need to be reallocated often, or you don't believe the cost of allocation is a bottleneck, using collect::<String>() here may be justified if you find it more readable and easier to reason about.
Let's go back to your original code.
let mut result = String::with_capacity(input.len());
for gc in input.graphemes(true).rev() {
result.push_str(gc);
}
This is idiomatic. collect is also idiomatic, but all collect does is basically the above, with a less accurate initial capacity. Since collect doesn't do what you want, it's not unidiomatic to write the code yourself.
There is a slightly more concise, iterator-y version that still makes only one allocation. Use the extend method, which is part of Extend<&str> for String:
fn reverse(input: &str) -> String {
let mut result = String::with_capacity(input.len());
result.extend(input.graphemes(true).rev());
result
}
I have a vague feeling that extend is nicer, but both of these are perfectly idiomatic ways of writing the same code. You should not rewrite it to use collect, unless you feel that expresses the intent better and you don't care about the extra allocation.
Related
Efficiency of flattening and collecting slices

How can I convert a Vec<T> into a C-friendly *mut T?

I have a Rust library that returns a u8 array to a C caller via FFI. The library also handles dropping the array after the client is done with it. The library has no state, so the client needs to own the array until it is passed back to the library for freeing.
Using box::from_raw and boxed::into_raw would be nice, but I couldn't manage to work out how to convert the array into the return type.
A Vec<T> is described by 3 values:
A pointer to its first element, that can be obtained with .as_mut_ptr()
A length, that can be obtained with .len()
A capacity, that can be obtained with .capacity()
In terms of a C array, the capacity is the size of memory allocated, while the length is the number of elements actually contained in the array. Both are counting in number of T. You normally would need to provide these 3 values to your C code.
If you want them to be equals, you can use .shrink_to_fit() on the vector to reduce its capacity as near as its size as possible depending on the allocator.
If you give back the ownership of the Vec<T> to your C code, don't forget to call std::mem::forget(v) on it once you have retrieved the 3 values described before, to avoid having its destructor running at the end of the function.
Afterwards, you can create back a Vec from these 3 values using from_raw_parts(..) like this:
let v = unsafe { Vec::<T>::from_raw_parts(ptr, length, capacity) };
and when its destructor will run the memory will be correctly freed. Be careful, the 3 values need to be correct for deallocation of memory to be correct. It's not very important for a Vec<u8>, but the destructor of Vec will run the destructor of all data it contains according to its length.

Resources