Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Arc::new() is slow with cloned vectors

Tags:

rust

After executing this:

use std::sync::Arc;
use std::time::Instant;
fn main() {
    let cap = 100000000;
    let b0 = vec![0; cap];
    let now = Instant::now();
    Arc::new(b0);
    println!("T0: {:?}", now.elapsed());
    let c0 = vec![0; cap];
    let _ = c0.clone(); // <- this makes it slow
    let now = Instant::now();
    Arc::new(c0);
    println!("T1: {:?}", now.elapsed());
}

The result is: T0: 5.971µs T1: 26.69574ms

Why the second Arc::new is slow if we clone c0 before?

Edit:

I tested it with:

  • Windows 10, rust 1.44.1 debug
  • Linux, rust 1.47-nightly release
  • Linux, rust 1.18
  • MacOS, rust 1.44.0-nightly release

T1's time increases linearly with the vector size.

like image 481
Blezz Avatar asked Aug 27 '20 08:08

Blezz


1 Answers

Note that you are not measuring the time taken by the Arc::new, but instead you are measuring the time taken when the Arc is dropped (since you don't assign it to anything).

Note also that depending on your system, this line:

let b0 = vec![0; cap];

may not allocate any physical memory: it can allocate only virtual space, with the physical memory being allocated and zeroed on the first time it is accessed. This is confirmed by cachegrind that shows almost no cache misses until the buffer is cloned.

Cloning the vector has two side effects:

  • It causes the physical memory to be mapped, which means that it must be unmapped when dropping the Arc,
  • It trashes the cache (due to the memory zeroing and copying), which causes cache misses in the subsequent code.

Using the following code, which moves the deallocation out of the measured timings, the times are much faster and closer:

use std::sync::Arc;
use std::time::Instant;

fn main() {
   let cap = 1000000000;
   let b0 = vec![0; cap];
   let now = Instant::now();
   let a = Arc::new(b0);
   println!("T0: {:?}", now.elapsed());
   drop (a);
   let c0 = vec![0; cap];
   let _ = c0.clone(); // <- this makes it slow
   let now2 = Instant::now();
   let a = Arc::new(c0);
   println!("T1: {:?}", now2.elapsed());
   drop (a);
}

The second Arc::new is still slower, but the difference can be explained by the 3 extra L3-cache misses reported by cachegrind.

like image 129
Jmb Avatar answered Nov 19 '22 08:11

Jmb