Skip to content

Commit

Permalink
Add constructor tests for Guarded<T>. Clean up the multi threaded ben…
Browse files Browse the repository at this point in the history
…chmark. Add developer notes.
  • Loading branch information
luketokheim committed Apr 11, 2023
1 parent 8ff0422 commit bbe59a5
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 68 deletions.
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Contributing

<!--
Short overview, rules, general guidelines, notes about pull requests and
style should go here.
-->
By submitting a pull request or a patch, you represent that you have the right
to license your contribution to the lockables project owners and the community,
agree that your contributions are licensed under the lockables license, and
agree to future changes to the licensing.

## Getting started

Expand Down
6 changes: 0 additions & 6 deletions HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,6 @@ file by default, which can be submitted to services with CI integration. The
HTML command uses the trace command's output to generate an HTML document to
`<binary-dir>/coverage_html` by default.

#### `docs`

Available if `BUILD_MCSS_DOCS` is enabled. Builds to documentation using
Doxygen and m.css. The output will go to `<binary-dir>/docs` by default
(customizable using `DOXYGEN_OUTPUT_DIRECTORY`).

#### `format-check` and `format-fix`

These targets run the clang-format tool on the codebase to check errors and to
Expand Down
76 changes: 39 additions & 37 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ library from Google. Most of the benchmarks compare ``std::mutex`` and
``std::shared_metux`` performance over various numbers of reader and writer
threads.

For example, the test named:
For example, the test case named:

```console
BM_Guarded_Threads<std::mutex>/MutexTest/2/threads:8
BM_Guarded_Fixture<std::mutex>/Scoped/2/threads:8
```

Runs 8 total threads, 2 writer threads + 6 reader threads, all accessing one
``Guarded<int>`` value protected by a ``std::mutex``.
Runs 8 total threads all accessing one ``Guarded<int>`` value protected by a
``std::mutex``. In this run there are 2 writer threads and 6 reader threads.


## Build

Expand All @@ -32,37 +33,38 @@ Will generate a report that looks something like this.

```console
Running ./build/Release/benchmarks/lockables-bench
Run on (8 X 24.1208 MHz CPU s)
Run on (8 X 4900 MHz CPU s)
CPU Caches:
L1 Data 64 KiB
L1 Instruction 128 KiB
L2 Unified 4096 KiB (x8)
Load Average: 1.45, 1.64, 1.73
-------------------------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations
-------------------------------------------------------------------------------------------------------------
BM_Guarded_Shared<int, std::mutex> 6.77 ns 6.77 ns 82091215
BM_Guarded_Shared<int, std::shared_mutex> 15.5 ns 15.5 ns 45115883
BM_Guarded_Exclusive<int, std::mutex> 6.77 ns 6.77 ns 101840402
BM_Guarded_Exclusive<int, std::shared_mutex> 16.9 ns 16.9 ns 42285852
BM_Guarded_Multiple<int, std::mutex> 13.9 ns 13.9 ns 50140752
BM_Guarded_Multiple<int, std::shared_mutex> 30.8 ns 30.8 ns 22705378
BM_Guarded_Threads<std::mutex>/MutexTest/2/threads:4 24.3 ns 80.3 ns 10367912
BM_Guarded_Threads<std::mutex>/MutexTest/2/threads:8 24.6 ns 130 ns 4945120
BM_Guarded_Threads<std::mutex>/MutexTest/2/threads:16 27.1 ns 221 ns 3233344
BM_Guarded_Threads<std::mutex>/MutexTest/4/threads:4 16.3 ns 49.8 ns 13972752
BM_Guarded_Threads<std::mutex>/MutexTest/4/threads:8 23.5 ns 128 ns 5604032
BM_Guarded_Threads<std::mutex>/MutexTest/4/threads:16 23.9 ns 209 ns 3329280
BM_Guarded_Threads<std::mutex>/MutexTest/8/threads:4 15.9 ns 48.6 ns 14068160
BM_Guarded_Threads<std::mutex>/MutexTest/8/threads:8 21.7 ns 94.3 ns 5667616
BM_Guarded_Threads<std::mutex>/MutexTest/8/threads:16 25.3 ns 175 ns 3429536
BM_Guarded_Threads<std::shared_mutex>/SharedMutexTest/2/threads:4 81.7 ns 180 ns 3780668
BM_Guarded_Threads<std::shared_mutex>/SharedMutexTest/2/threads:8 216 ns 742 ns 930712
BM_Guarded_Threads<std::shared_mutex>/SharedMutexTest/2/threads:16 241 ns 1008 ns 681584
BM_Guarded_Threads<std::shared_mutex>/SharedMutexTest/4/threads:4 122 ns 259 ns 2866504
BM_Guarded_Threads<std::shared_mutex>/SharedMutexTest/4/threads:8 327 ns 1143 ns 753288
BM_Guarded_Threads<std::shared_mutex>/SharedMutexTest/4/threads:16 341 ns 1279 ns 529440
BM_Guarded_Threads<std::shared_mutex>/SharedMutexTest/8/threads:4 123 ns 262 ns 2809836
BM_Guarded_Threads<std::shared_mutex>/SharedMutexTest/8/threads:8 405 ns 1455 ns 473328
BM_Guarded_Threads<std::shared_mutex>/SharedMutexTest/8/threads:16 534 ns 1958 ns 376960
```
L1 Data 32 KiB (x8)
L1 Instruction 32 KiB (x8)
L2 Unified 256 KiB (x8)
L3 Unified 12288 KiB (x1)
Load Average: 0.09, 0.13, 0.20
----------------------------------------------------------------------------------------------------
Benchmark Time CPU Iterations
----------------------------------------------------------------------------------------------------
BM_Guarded_Shared<int, std::mutex> 4.53 ns 4.53 ns 157135470
BM_Guarded_Shared<int, std::shared_mutex> 12.8 ns 12.8 ns 54742381
BM_Guarded_Exclusive<int, std::mutex> 4.53 ns 4.53 ns 154471766
BM_Guarded_Exclusive<int, std::shared_mutex> 18.9 ns 18.9 ns 36433855
BM_Guarded_Multiple<int, std::mutex> 14.3 ns 14.3 ns 48662736
BM_Guarded_Multiple<int, std::shared_mutex> 37.2 ns 37.2 ns 18799451
BM_Guarded_Fixture<std::mutex>/Scoped/2/threads:4 51.1 ns 183 ns 4020044
BM_Guarded_Fixture<std::mutex>/Scoped/2/threads:8 53.4 ns 361 ns 1772704
BM_Guarded_Fixture<std::mutex>/Scoped/2/threads:16 52.7 ns 379 ns 1957728
BM_Guarded_Fixture<std::mutex>/Scoped/4/threads:4 50.7 ns 181 ns 3919188
BM_Guarded_Fixture<std::mutex>/Scoped/4/threads:8 54.5 ns 398 ns 1781784
BM_Guarded_Fixture<std::mutex>/Scoped/4/threads:16 50.1 ns 396 ns 1734576
BM_Guarded_Fixture<std::mutex>/Scoped/8/threads:4 50.0 ns 179 ns 3807104
BM_Guarded_Fixture<std::mutex>/Scoped/8/threads:8 56.5 ns 411 ns 1787320
BM_Guarded_Fixture<std::mutex>/Scoped/8/threads:16 52.5 ns 418 ns 1805072
BM_Guarded_Fixture<std::shared_mutex>/Shared/2/threads:4 105 ns 244 ns 2942380
BM_Guarded_Fixture<std::shared_mutex>/Shared/2/threads:8 79.2 ns 439 ns 1665912
BM_Guarded_Fixture<std::shared_mutex>/Shared/2/threads:16 76.1 ns 713 ns 1153584
BM_Guarded_Fixture<std::shared_mutex>/Shared/4/threads:4 166 ns 653 ns 1255744
BM_Guarded_Fixture<std::shared_mutex>/Shared/4/threads:8 100 ns 493 ns 1364160
BM_Guarded_Fixture<std::shared_mutex>/Shared/4/threads:16 74.9 ns 660 ns 1159248
BM_Guarded_Fixture<std::shared_mutex>/Shared/8/threads:4 159 ns 627 ns 1233704
BM_Guarded_Fixture<std::shared_mutex>/Shared/8/threads:8 158 ns 1200 ns 606584
BM_Guarded_Fixture<std::shared_mutex>/Shared/8/threads:16 111 ns 1126 ns 709856
```
69 changes: 48 additions & 21 deletions benchmarks/bench_guarded.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#include <benchmark/benchmark.h>
#include <lockables/guarded.hpp>

#include <iostream>

template <typename T, typename Mutex>
void BM_Guarded_Shared(benchmark::State& state) {
lockables::Guarded<T, Mutex> value;
Expand Down Expand Up @@ -50,49 +48,78 @@ void BM_Guarded_Multiple(benchmark::State& state) {
BENCHMARK(BM_Guarded_Multiple<int, std::mutex>);
BENCHMARK(BM_Guarded_Multiple<int, std::shared_mutex>);

// Use a fixture to shared the Guarded<T> value across multiple threads that are
// spawned by the benchmark library. Benchmark also guarantees that all threads
// are running before any enter their state loop.
//
// https://github.com/google/benchmark/blob/main/docs/user_guide.md#fixtures
template <typename Mutex>
struct BM_Guarded_Threads : benchmark::Fixture {
struct BM_Guarded_Fixture : benchmark::Fixture {
lockables::Guarded<int64_t, Mutex> value{};

void SetUp(const benchmark::State& state) override {
if (auto guard = value.with_exclusive()) {
*guard = 0;
}
}

void TearDown(const benchmark::State& state) override {
if (auto guard = value.with_exclusive()) {
assert(*guard == state.iterations() * state.range(0));
}
}

void BenchmarkCase(benchmark::State& state) override {
// First argument controls how many threads are writers.
// For example, 8 threads and 4 writers -> 4 readers
const bool is_writer = state.thread_index() < state.range(0);

if (is_writer) {
RunWriter(state);
} else {
RunReader(state);
}
}

void RunWriter(benchmark::State& state) {
for (auto _ : state) {
int64_t copy{};
if (auto guard = value.with_exclusive()) {
*guard += 1;
copy = *guard;
}

benchmark::DoNotOptimize(copy);
}
}

void RunReader(benchmark::State& state) {
for (auto _ : state) {
int64_t copy{};
if (is_writer) {
if (auto guard = value.with_exclusive()) {
*guard = 100;
copy = 1;
}
} else {
if (auto guard = value.with_shared()) {
copy = *guard;
}
if (auto guard = value.with_shared()) {
copy = *guard;
}

benchmark::DoNotOptimize(copy);
}
}
};

BENCHMARK_TEMPLATE_DEFINE_F(BM_Guarded_Threads, MutexTest, std::mutex)
(benchmark::State& state) { BM_Guarded_Threads::BenchmarkCase(state); }
BENCHMARK_TEMPLATE_DEFINE_F(BM_Guarded_Fixture, Scoped, std::mutex)
(benchmark::State& state) { BM_Guarded_Fixture::BenchmarkCase(state); }

// Run 4-16 threads with [2, 4, ..., 16) writers and the rest readers.
BENCHMARK_REGISTER_F(BM_Guarded_Threads, MutexTest)
// Run 4-16 threads with [2, 4, 8] writers and the rest readers.
BENCHMARK_REGISTER_F(BM_Guarded_Fixture, Scoped)
->ThreadRange(4, 16)
->Arg(2)
->Arg(4)
->Arg(8);

BENCHMARK_TEMPLATE_DEFINE_F(BM_Guarded_Threads, SharedMutexTest,
std::shared_mutex)
(benchmark::State& state) { BM_Guarded_Threads::BenchmarkCase(state); }
BENCHMARK_TEMPLATE_DEFINE_F(BM_Guarded_Fixture, Shared, std::shared_mutex)
(benchmark::State& state) { BM_Guarded_Fixture::BenchmarkCase(state); }

// Run 4-16 threads with [2, 4, ..., 16) writers and the rest readers.
BENCHMARK_REGISTER_F(BM_Guarded_Threads, SharedMutexTest)
// Run 4-16 threads with [2, 4, 8] writers and the rest readers.
BENCHMARK_REGISTER_F(BM_Guarded_Fixture, Shared)
->ThreadRange(4, 16)
->Arg(2)
->Arg(4)
Expand Down
102 changes: 102 additions & 0 deletions tests/test_guarded.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
#include <lockables/guarded.hpp>

#include <future>
#include <memory>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>

TEMPLATE_PRODUCT_TEST_CASE("read and write PODs", "[lockables][Guarded]",
Expand Down Expand Up @@ -246,4 +248,104 @@ TEMPLATE_TEST_CASE("all the mutex types", "[lockables][Guarded]", std::mutex,
copy = lockables::with_exclusive([](int& x) { return x; }, value);

CHECK(copy == 20);
}

TEST_CASE("constructor", "[lockables][examples][Guarded]") {
{
lockables::Guarded<int> value{-1};
if (auto guard = value.with_shared()) {
CHECK(*guard == -1);
}
}

{
const int x = 10;

lockables::Guarded<int> value{x};
if (auto guard = value.with_shared()) {
CHECK(*guard == 10);
}
}

{
auto value = std::make_unique<lockables::Guarded<int>>(101);
if (auto guard = value->with_shared()) {
CHECK(*guard == 101);
}
}

{
auto value = std::make_shared<lockables::Guarded<int>>(101);
if (auto guard = value->with_shared()) {
CHECK(*guard == 101);
}
}

{
const std::vector<int> x(100, 1);

lockables::Guarded<std::vector<int>> value{x};
if (auto guard = value.with_shared()) {
CHECK(*guard == std::vector<int>(100, 1));
}
}

{
lockables::Guarded<std::vector<int>> value{std::vector<int>{1, 2, 3}};
if (auto guard = value.with_shared()) {
CHECK(*guard == std::vector<int>{1, 2, 3});
}
}

{
lockables::Guarded<std::vector<int>> value{4, 5, 6};
if (auto guard = value.with_shared()) {
CHECK(*guard == std::vector<int>{4, 5, 6});
}
}

{
using hash_map = std::unordered_map<std::string, int>;

lockables::Guarded<hash_map> value{hash_map{{"Hello", 15}, {"World", 10}}};
if (auto guard = value.with_shared()) {
CHECK(guard->at("Hello") == 15);
}

if (auto guard = value.with_exclusive()) {
CHECK((*guard)["Hello"] == 15);
CHECK((*guard)["Nope"] == 0);
}
}

{
using hash_map = std::unordered_map<std::string, int>;
hash_map map{{"Hello", 15}, {"World", 10}};

lockables::Guarded<hash_map> value{map};
if (auto guard = value.with_shared()) {
CHECK(guard->at("Hello") == 15);
}
}

{
using hash_map = std::unordered_map<std::string, int>;
hash_map map{{"Hello", 15}, {"World", 10}};

lockables::Guarded<hash_map> value{std::move(map)};
if (auto guard = value.with_shared()) {
CHECK(guard->at("Hello") == 15);
}
}

{
using hash_map = std::unordered_map<std::string, int>;
auto map =
std::make_unique<hash_map>(hash_map{{"Hello", 15}, {"World", 10}});

lockables::Guarded<std::unique_ptr<hash_map>> value{std::move(map)};
if (auto guard = value.with_shared()) {
CHECK((*guard)->at("Hello") == 15);
}
}
}

0 comments on commit bbe59a5

Please sign in to comment.