Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is casting between integers expensive?

Tags:

rust

I am working on a project where I am doing a lot of index-based calculation. I have a few lines like:

let mut current_x: usize = (start.x as isize + i as isize * delta_x) as usize;

start.x and i are usizes and delta_x is of type isize. Most of my data is unsigned, therefore storing it signed would not make much sense. On the other hand, when I manipulate an array I am accessing a lot I have to convert everything back to usize as seen above.

Is casting between integers expensive? Does it have an impact on runtime performance at all?

Are there other ways to handle index arithmetics easier / more efficiently?

like image 847
Pethor Avatar asked Jun 21 '17 20:06

Pethor


1 Answers

It depends

It's basically impossible to answer your question in isolation. These types of low-level things can be aggressively combined with operations that have to happen anyway, so any amount of inlining can change the behavior. Additionally, it strongly depends on your processor; changing to a 64-bit number on an 8-bit microcontroller is probably pretty expensive!

My general advice is to not worry. Keep your types consistent, get the right answers, then profile your code and fix the issues you find.

Pragmatically, what are you going to do instead?


That said, here's some concrete stuff for x86-64 and Rust 1.18.0.

Same size, changing sign

Basically no impact. If these were inlined, then you probably would never even see any assembly.

#[inline(never)]
pub fn signed_to_unsigned(i: isize) -> usize {
    i as usize
}

#[inline(never)]
pub fn unsigned_to_signed(i: usize) -> isize {
    i as isize
}

Each generates the assembly

movq    %rdi, %rax
retq

Extending a value

These have to sign- or zero-extend the value, so some kind of minimal operation has to occur to fill those extra bits:

#[inline(never)]
pub fn u8_to_u64(i: u8) -> u64 {
    i as u64
}

#[inline(never)]
pub fn i8_to_i64(i: i8) -> i64 {
    i as i64
}

Generates the assembly

movzbl  %dil, %eax
retq

movsbq  %dil, %rax
retq

Truncating a value

Truncating is again just another move, basically no impact.

#[inline(never)]
pub fn u64_to_u8(i: u64) -> u8 {
    i as u8
}

#[inline(never)]
pub fn i64_to_i8(i: i64) -> i8 {
    i as i8
}

Generates the assembly

movl    %edi, %eax
retq

movl    %edi, %eax
retq

All these operations boil down to a single instruction on x86-64. Then you get into complications around "how long does an operation take" and that's even harder.

like image 67
Shepmaster Avatar answered Sep 22 '22 14:09

Shepmaster