Skip to content

Instantly share code, notes, and snippets.

@azat
Last active January 28, 2024 15:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save azat/51a5fcc3a40af9f678906a3a6e14e079 to your computer and use it in GitHub Desktop.
Save azat/51a5fcc3a40af9f678906a3a6e14e079 to your computer and use it in GitHub Desktop.
Test various locks (under contention and not)
#include <boost/smart_ptr/detail/spinlock.hpp>
#include <cstdint>
#include <cstring>
#include <cassert>
#include <iostream>
#include <list>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <thread>
uint64_t now_ns()
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
return ts.tv_sec * (uint64_t)1e9 + ts.tv_nsec;
}
struct Job
{
std::optional<std::thread> thread;
uint64_t elapsed_ns = 0;
};
template <class L>
class SharedScopedLock
{
public:
SharedScopedLock(L & l) : l(l) { l.lock_shared(); }
~SharedScopedLock() { l.unlock_shared(); }
private:
L & l;
};
template <class L> void init_lock(L & l) {}
template <> void init_lock(boost::detail::spinlock & l)
{
boost::detail::spinlock init = BOOST_DETAIL_SPINLOCK_INIT;
std::memcpy(&l, &init, sizeof(init));
}
template <class L, class SL>
void test_lock(const char * name, size_t threads, size_t iterations = 10'000)
{
L lock;
init_lock(lock);
std::list<Job> jobs(threads);
for (auto & job : jobs)
{
job.thread.emplace([&lock, &job, iterations]()
{
uint64_t start = now_ns();
for (size_t i = 0; i < iterations; ++i)
SL sp(lock);
uint64_t end = now_ns();
assert(end >= start);
job.elapsed_ns += end - start;
});
}
for (auto & job : jobs)
{
job.thread->join();
}
uint64_t elapsed_ns = 0;
for (auto & job : jobs)
elapsed_ns += job.elapsed_ns;
std::cout
<< name << "(threads=" << threads << ")"
<< "/elapsed: " << elapsed_ns/1e6 << " ms, "
<< iterations/(elapsed_ns/1e9) << "ops, "
<< elapsed_ns/iterations << "ns per call\n";
}
void test_boost_spinlock()
{
using lock_t = boost::detail::spinlock;
using scoped_lock_t = boost::detail::spinlock::scoped_lock;
test_lock<lock_t, scoped_lock_t>("boost::atomic_shared_ptr::spinlock", 1);
test_lock<lock_t, scoped_lock_t>("boost::atomic_shared_ptr::spinlock", 100);
}
void test_std_lock()
{
using lock_t = std::mutex;
using scoped_lock_t = std::scoped_lock<lock_t>;
test_lock<lock_t, scoped_lock_t>("std::mutex", 1);
test_lock<lock_t, scoped_lock_t>("std::mutex", 100);
}
void test_std_rw_lock_exclusive()
{
using lock_t = std::shared_mutex;
using scoped_lock_t = std::scoped_lock<lock_t>;
test_lock<lock_t, scoped_lock_t>("std::shared_mutex/exclusive_lock", 1);
test_lock<lock_t, scoped_lock_t>("std::shared_mutex/exclusive_lock", 100);
}
void test_std_rw_lock_shared()
{
using lock_t = std::shared_mutex;
using scoped_lock_t = SharedScopedLock<lock_t>;
test_lock<lock_t, scoped_lock_t>("std::shared_mutex/shared_lock", 1);
test_lock<lock_t, scoped_lock_t>("std::shared_mutex/shared_lock", 100);
}
int main()
{
test_boost_spinlock();
test_std_lock();
test_std_rw_lock_exclusive();
test_std_rw_lock_shared();
return 0;
}
$ clang++ /tmp/test-contention.cpp -o /tmp/test-contention -O3 -g3
$ /tmp/test-contention
boost::atomic_shared_ptr::spinlock(threads=1)/elapsed: 0.083276 ms, 1.20083e+08ops, 8ns per call
boost::atomic_shared_ptr::spinlock(threads=100)/elapsed: 469.24 ms, 21311ops, 46924ns per call
std::mutex(threads=1)/elapsed: 0.061976 ms, 1.61353e+08ops, 6ns per call
std::mutex(threads=100)/elapsed: 2548.34 ms, 3924.13ops, 254833ns per call
std::shared_mutex/exclusive_lock(threads=1)/elapsed: 0.065312 ms, 1.53111e+08ops, 6ns per call
std::shared_mutex/exclusive_lock(threads=100)/elapsed: 101700 ms, 98.3285ops, 10169990ns per call
std::shared_mutex/shared_lock(threads=1)/elapsed: 0.057047 ms, 1.75294e+08ops, 5ns per call
std::shared_mutex/shared_lock(threads=100)/elapsed: 4573.88 ms, 2186.33ops, 457388ns per call
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment