cognitive-engineering-lab / rust-book

The Rust Programming Language: Experimental Edition
https://rust-book.cs.brown.edu
Other
503 stars 82 forks source link

Bad exemple / Misleading use of Box::new in 4.1. What is Ownership #165

Open Ily3s opened 4 months ago

Ily3s commented 4 months ago

https://rust-book.cs.brown.edu/ch04-01-what-is-ownership.html#boxes-live-in-the-heap

Here it introduces to boxes by showing us how we can move an array of 1 million integers from a variable a to a variable b. It suggests that doing that using boxes is more efficient than just doing a copy on the stack because moving a box doesn't actually do a copy.

The problem is this following line of code : let a = Box::new([0; 1_000_000]);. Looking at its assembly output, I discovered this actually sets 4 million bytes to zero on the stack before allocating 4MB on the heap and then actually performing a copy ! This happens only on debug mode. In release mode however, it doesn't happen only because the value we're assigning every integer of the array to is 0, but with anything else than 0, it does pretty much the same thing as in debug.

Since I'm never certain of what I understand from assembly, I verified this by replacing the value of 1 million by 10 millions, which as I expected, caused a stack overflow !

This means that the solution with Box is effectively worse than the one with just a raw copy on the stack. I find that to be pretty misleading since it's shown as being the solution to avoid copying, whereas, not only does it copies, but it copies from the stack to the heap (which is worse than a stack to stack copy). I think this should be done using vec! instead.

Edit: When I think about it I realise this moving over copying issue might just be a memory issue rather than a performance issue since allocating on the heap is probabely always slower anyway. Still I think it is relevant to point at the fact that with Box::new, at some point, 4MB are pushed onto the stack, which could be avoided with the use of vec!

willcrichton commented 1 week ago

To be clear, it only copies from the stack to the heap when constructing the box. Once the heap object is prepared, then pointers to it can be freely copied with no cost.

You're right that the stack-to-heap copy can be expensive. Rust does not have C++'s concept of "placement new", but not for lack of trying. See: https://github.com/rust-lang/rust/issues/27779#issuecomment-378416911

I will probably keep the book the same because the example still satisfies its intended purpose: showing the difference in behavior of copies between owned and boxed arrays.