It might be something related to IRLO, but I fear that experts already have a better solution than I am.
I had written C extension for R several years ago, and found something very useful:
R store a pointer ,*x
, to the data, and store the data's length in x[-1]
.
Could we have the same implementation for Rust?
Currently, we have to dereference a pointer twice to get data from &Vec. If such implementation is used, we could index element from &Vec directly, which may save sometime.
Is it possible? Could we add an additional layer for Vecs?
IMHO, the memory layout could be:
struct VecSlice{
cap:usize,
len:usize,
data:[T]// store data here directly.
}
impl Borrow for VecSlice{
fn borrow(&self)->...suppose it could compile{
&self.data
}
}
impl VecSlice{
fn len(&self)->usize{
unsafe{*(self as *usize).sub(1)}
}
}
struct Vec{
vec_slice:*T // point to VecSlice .data field
}
minimal example to show the disadvantage of current Vec
implementation
// we have to perform 2 instruction to get a number from &Vec<u8>, but only one instruction is enough for &[u8]
// Type your code here, or load an example.
pub fn vec(num: &Vec<u8>) -> u8 {
if num.len()<=12 {
unsafe{std::hint::unreachable_unchecked()}
}
num[12]
}
pub fn slice(num: &[u8]) -> u8 {
if num.len()<=12 {
unsafe{std::hint::unreachable_unchecked()}
}
num[12]
}
/* godbolt
example::vec:
mov rax, qword ptr [rdi]
mov al, byte ptr [rax + 12]
ret
example::slice:
mov al, byte ptr [rdi + 12]
ret
*/
real case:
we may use Vec<Vec<_>> to store arrays with different length, which seems to be very annoying:
let a=vec![vec![0],vec![1,2,3]];
if we want to get a[1][2]
, we actually compute something like:
let a1=a.buf.get(1);// a1== & vec![1,2,3]
let a2=a1.buf.get(2);// a2= & 3
let res=*a2 ; // res=3
This is a lot of waste, we might have a better implementation:
let a=&[&[0],&[1,2,3]]; // let us suppose it could be.
here, we could dereference the pointer in 2 instructions:
let a1=a.get(1);//a1=&[1,2,3]
let a2=a1[2];// since a1 is a pointer, we could directly dereference it, and get the answer, 3.
update before someone blame me:
the extra consumption is a global constant, EMPTY:
const EMPTY:VecSlice<()>=VecSlice{cap:0,len:0,data:[()]};
index, panic
push increase len, panic
pop decrease len, panic
grow the slice would alloc a memory directly, won't affect EMPTY (thus Vec::<T>::new() == EMPTY holds.
)
Thus we could also have the ability to alloc a Vec
when it is not empty