#include #include #include // std::shared_lock #include #include #include // std::async #include #include // std::string #include "capo/spin_lock.hpp" #include "capo/type_name.hpp" #include "libipc/rw_lock.h" #include "test.h" #include "thread_pool.h" namespace { constexpr int LoopCount = 100000; constexpr int ThreadMax = 8; template constexpr T acc(T b, T e) noexcept { return (e + b) * (e - b + 1) / 2; } template struct lc_wrapper : Mutex { void lock_shared () { Mutex::lock (); } void unlock_shared() { Mutex::unlock(); } }; template void benchmark_lc(int w, int r, char const * message) { ipc_ut::sender().start(static_cast(w)); ipc_ut::reader().start(static_cast(r)); ipc_ut::test_stopwatch sw; std::uint64_t data = 0; std::uint64_t sum = acc(1, Loops) * w; Lc lc; for (int k = 0; k < r; ++k) { ipc_ut::reader() << [sum, &lc, &data] { while (1) { { std::shared_lock guard { lc }; if (data == sum) break; } std::this_thread::yield(); } }; } for (int k = 0; k < w; ++k) { ipc_ut::sender() << [&sw, &lc, &data] { sw.start(); for (int i = 1; i <= Loops; ++i) { { std::lock_guard guard { lc }; data += i; } std::this_thread::yield(); } }; } ipc_ut::sender().wait_for_done(); sw.print_elapsed(w, r, Loops, message); ipc_ut::reader().wait_for_done(); } void test_lock_performance(int w, int r) { std::cout << "test_lock_performance: [" << w << "-" << r << "]" << std::endl; benchmark_lc(w, r, "ipc::rw_lock"); benchmark_lc>(w, r, "ipc::spin_lock"); benchmark_lc>(w, r, "capo::spin_lock"); benchmark_lc >(w, r, "std::mutex"); // benchmark_lc(w, r, "std::shared_mutex"); } } // internal-linkage //TEST(Thread, rw_lock) { // for (int i = 1; i <= ThreadMax; ++i) test_lock_performance(i, 0); // for (int i = 1; i <= ThreadMax; ++i) test_lock_performance(1, i); // for (int i = 2; i <= ThreadMax; ++i) test_lock_performance(i, i); //} #if 0 // disable ipc::tls TEST(Thread, tls_main_thread) { ipc::tls::pointer p; EXPECT_FALSE(p); EXPECT_NE(p.create(123), nullptr); EXPECT_EQ(*p, 123); } namespace { struct Foo { std::atomic * px_; Foo(std::atomic * px) : px_(px) {} ~Foo() { px_->fetch_add(1, std::memory_order_relaxed); } }; } // namespace TEST(Thread, tls_create_once) { std::atomic x { 0 }; Foo { &x }; EXPECT_EQ(x, 1); { ipc::tls::pointer foo; EXPECT_NE(foo.create(&x), nullptr); EXPECT_EQ(x, 1); std::thread {[&foo, &x] { auto foo1 = foo.create_once(&x); auto foo2 = foo.create_once(&x); EXPECT_EQ(foo1, foo2); }}.join(); EXPECT_EQ(x, 2); } EXPECT_EQ(x, 3); } TEST(Thread, tls_multi_thread) { std::atomic x { 0 }; { ipc::tls::pointer foo; // no create EXPECT_EQ(x, 0); ipc_ut::thread_pool pool { 10 }; for (int i = 0; i < 1000; ++i) { pool << [&foo, &x] { // foo.create_once(&x); std::this_thread::sleep_for(std::chrono::milliseconds(1)); }; } pool.wait_for_done(); EXPECT_EQ(x, 0); // thread_pool hasn't destructed. } // EXPECT_EQ(x, 10); } namespace { template void benchmark_tls(char const * message) { ipc_ut::thread_pool pool { static_cast(ThreadMax) }; ipc_ut::test_stopwatch sw; pool.wait_for_started(); for (int k = 0; k < ThreadMax; ++k) { pool << [&sw] { sw.start(); for (int i = 0; i < LoopCount * 10; ++i) { *Tls::template get() = i; } }; } pool.wait_for_done(); sw.print_elapsed(ThreadMax, LoopCount * 10, message); } struct ipc_tls { template static T * get() { static ipc::tls::pointer p; return p.create_once(); } }; struct std_tls { template static T * get() { thread_local T p; return &p; } }; struct Str { std::string str_; Str & operator=(int i) { str_ = std::to_string(i); return *this; } }; } // internal-linkage TEST(Thread, tls_benchmark) { benchmark_tls("std_tls: int"); benchmark_tls("ipc_tls: int"); benchmark_tls("std_tls: Str"); benchmark_tls("ipc_tls: Str"); } #endif