Note: this is not a practical problem(I have never locked more than 2 mutexes with scoped_lock), I am mostly curious why implementations of scoped_lock apparently have a relatively big performance hit when locking mutexes in different order.
Example code below, godbolt link.
#include<mutex>
#include<thread>
#include<chrono>
#include<iostream>
std::mutex m1, m2, m3, m4, m5, m6;
int cnt =0;
void f(){
for (int i=0; i< 500*1000; ++i){
std::scoped_lock sl{m1, m2, m3, m4, m5, m6};
cnt++;
}
}
void f_unord(){
for (int i=0; i< 500*1000; ++i){
std::scoped_lock sl{m4, m5, m6, m1, m2, m3};
cnt++;
}
}
int main(){
for (int run = 0; run<4; ++run)
{
{
const auto start = std::chrono::steady_clock::now();
std::thread t1(f), t2(f);
t1.join();
t2.join();
const auto end = std::chrono::steady_clock::now();
std::cout << "same lock order: " << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count() << std::endl;
std::cout << cnt << std::endl;
}
{
const auto start = std::chrono::steady_clock::now();
std::thread t1(f), t2(f_unord);
t1.join();
t2.join();
const auto end = std::chrono::steady_clock::now();
std::cout << "different lock order: " << std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count() << std::endl;
std::cout << cnt << std::endl;
}
}
}
note on why this is surprising: I would expect that since mutexes are not movable for implementations to just sort the mutexes by address and use that order of locking.
note on benchmarking on godbolt: I know godbolt is not reliable, I get similar results on my machine in VM:
g++ --version; g++ -O2 -std=c++17 scoped_lock.cpp -pthread; ./a.out
g++ (Ubuntu 9.2.1-9ubuntu2) 9.2.1 20191008 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
different lock order: 1074
1000000
same lock order: 602
2000000
different lock order: 987
3000000
same lock order: 612
4000000
different lock order: 1012
5000000
same lock order: 585
6000000
different lock order: 1050
7000000
same lock order: 675
8000000
different lock order: 1107
9000000
same lock order: 609
10000000