Why is std::sync::Mutex 60-70x time slower than C++'s std::mutex?

161 points cbeuw

Partially solved: As people have speculated in the comments, it appears that C++ is indeed cheating. If we wrap the whole thing into a thread and join it at the end, we see the same performance: Rust C++

​

use std::sync::Mutex;
use std::time::Instant;

pub fn main() {
    let mut mutexes = Vec::new();
    for _ in 0..1_000_000 {
        mutexes.push(Mutex::new(()))
    }

    let begin_time = Instant::now();
    for mutex in &mutexes {
        let _ = mutex.lock().unwrap();
    }
    let duration = begin_time.elapsed();

    println!("{} us", duration.as_micros());
}

This code puts 1 million std::sync::Mutexes on the heap, locks and immediately unlocks them in a loop. With Rust 1.56.1 it takes around 15ms for the loop part (this is 30x instead of 60x slower because I made a mistake in code when I posted and I can't edit the title. See the edit note below).

$ rustc -C opt-level=3 mutex.rs
$ ./mutex
15288 us

The equivalent C++ code looks like this, using std::scoped_lock

#include <chrono>
#include <iostream>
#include <mutex>
#include <vector>

int main() {
  std::vector<std::mutex> mutexes(1'000'000);

  auto begin_time = std::chrono::high_resolution_clock::now();
  for (auto &mutex : mutexes) {
    std::scoped_lock<std::mutex> lock(mutex);
  }
  auto end_time = std::chrono::high_resolution_clock::now();

  auto duration = end_time - begin_time;
  auto micros =
      std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
  std::cout << micros << " us" << std::endl;

  return 0;
}

This takes only 450μs with Clang 10.0.0

$ clang++ -std=c++17 -O2 mutex.cpp 
$ ./a.out
456 us

Note that neither compiler optimised out the mutex locks as we can see pthread_mutex_lock being called in both disassemblies: Rust C++. So we are indeed measuring the mutex

Edit: u/SkiFire13 noted in a comment that in a previous version of my code I moved mutexes vector into the iterator and hence was measuring the time to destruct the vector as well. I have changed the code to borrow it instead and it's now \~30x slower instead of 60x