I was looking at next() implementation for Iter<'a, T: 'a> in slice.
next()
calls next_unchecked()
which calls self.post_inc_start(1)
which calls self.ptr.add(offset)
here offset=1
. After that pointer checks till which point in memory it should copy for offset=1 and then returns that part.
This is what I got from reading macro.rs for slices.
If instead of using self.ptr.add(offset)
we stored size of elements of slice and then just directly coped memory how much would it affect efficiency.
As an optimization, the compiler inlines functions. That means that when next()
calls post_inc_start()
, then the compiler will copy the code of post_inc_start()
into the code of next()
in place of the function call. Thus, there is no run-time overhead of the function call; furthermore, the compiler can examine the code of the two functions together and optimize them further into more efficient forms that would be impossible if they were two independent functions.
Note in particular that post_inc_start
asks to be inlined:
#[inline(always)]
unsafe fn post_inc_start(&mut self, offset: usize) -> NonNull<T> {
The #[inline]
attribute is just a hint that the compiler is not guaranteed to obey, but this at least tells you that this code was designed with inlining in mind.
In general, inlining is an extremely important optimization, and the foundation for many other optimizations. It allows us to write abstractions that make the code clearer, and easier to check the correctness of, without requiring additional run-time overhead.
So using self.ptr.add(offset)
is same as
intrinsics::offset(self, 1)
and so on.
I have a follow up question how does
intrinsics::offset(self, 1)
work. How does it know by how much to move in memory.
Is the size for every pointer stored near it's location and then checks it before moving in memory. If so then in slice size of every pointer is same . Won't it be more efficient to store size at one place
In this case, the size is known at compile time — it is a property of the type of what the pointer points to. So the information does not need to be stored in the pointer.
So when intrinsics::offset(self, 1)
is called is the value of 1 actually passed somewhere or is that also optimized away. So basically on calling the function program looks at some fixed point in memory to get a number and then moves in memory by that number of steps. Where that fixed point is does not depend on pointer in case of slice. It's just some fixed location.
is the value of 1 actually passed somewhere or is that also optimized away.
It will (almost certainly) be optimized away given that it's a constant. But that is not fundamentally a fact about pointers; the same thing happens to arithmetic. This function will not pass 10
anywhere, but embed it in an addition instruction:
fn foo(x: i32) -> i32 {
x + 10
}
and this is not because the +
operator is special, but because everything is optimized in that kind of way.
So the real part of code corresponding to '' intrinsics::offset(self, 1)
'' in program at runtime also does not know type of pointer either. It just knows a memory location which it can access to see size of steps
If by "code corresponding" you mean the compiled code, then of course - this "memory location" is right here, in the instruction itself. There's no types in assembly, after all, everything is just bytes.