diff --git a/.clang-format b/.clang-format index cf0b185..ef8caed 100644 --- a/.clang-format +++ b/.clang-format @@ -20,6 +20,7 @@ BraceWrapping: BeforeLambdaBody: true AlignEscapedNewlines: Right ContinuationIndentWidth: 2 +ConstructorInitializerIndentWidth: 2 AlignAfterOpenBracket: Align # TODO(MBkkt) Try BlockIndent clang-format 15 IncludeCategories: @@ -32,3 +33,4 @@ IncludeCategories: - Regex: '.*' # other libraries (system headers in our case) Priority: 50 IncludeBlocks: Regroup +IndentPPDirectives: AfterHash diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4494c6d..c0bd377 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @MBkkt +* @MBkkt diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index e12805b..fae13f2 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -1,12 +1,13 @@ name: Format on: + workflow_dispatch: push: - branches: [ master, main ] - paths-ignore: [ '**/result/**', '**.md' ] + branches: [ main ] + paths: [ '**.cpp', '**.hpp' ] pull_request: - branches: [ master, main ] - paths-ignore: [ '**/result/**', '**.md' ] + branches: [ main ] + paths: [ '**.cpp', '**.hpp' ] jobs: # TODO(MBkkt) clang-format 14, now this extension doesn't work, because clang-14 not really released yet @@ -16,7 +17,7 @@ jobs: # TODO(MBkkt) Add option or workflow for reformat main: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -24,9 +25,9 @@ jobs: - '.' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Run clang-format style check - uses: jidicula/clang-format-action@v4.5.0 + uses: jidicula/clang-format-action@v4.6.2 with: clang-format-version: 13 check-path: ${{ matrix.path }} diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8ba0b22..94f2a27 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -1,12 +1,13 @@ -name: Sanitizer +name: Linux on: + workflow_dispatch: push: - branches: [ master, main ] - paths-ignore: [ '**/result/**', '**.md' ] + branches: [ main ] + paths: [ '**.cpp', '**.hpp', '**.cmake', '**/CMakeLists.txt' ] pull_request: - branches: [ master, main ] - paths-ignore: [ '**/result/**', '**.md' ] + branches: [ main ] + paths: [ '**.cpp', '**.hpp', '**.cmake' ] jobs: main: @@ -14,41 +15,55 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-20.04 ] + os: [ ubuntu-22.04 ] build_type: [ Debug, RelWithDebInfo ] compiler: [ clang_libcxx, clang_libstdcxx, gcc_libstdcxx ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Update deps on Linux run: | sudo apt-get update sudo wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - - sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main" + sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-14 main" sudo apt-get update sudo apt-get install ninja-build gcc-11 g++-11 libstdc++-11-dev clang-14 lld-14 libc++-14-dev libc++abi-14-dev libboost-thread-dev sudo ln -sf /usr/bin/lld-14 /usr/local/bin/ld + sudo update-alternatives \ + --install /usr/bin/gcc gcc /usr/bin/gcc-11 200 \ + --slave /usr/bin/g++ g++ /usr/bin/g++-11 \ + --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-11 \ + --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-11 \ + --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-11 \ + --slave /usr/bin/gcov gcov /usr/bin/gcov-11 \ + --slave /usr/bin/gcov-tool gcov-tool /usr/bin/gcov-tool-11 \ + --slave /usr/bin/gcov-dump gcov-dump /usr/bin/gcov-dump-11 + sudo update-alternatives --auto gcc + sudo update-alternatives \ + --install /usr/bin/cpp cpp /usr/bin/cpp-11 200 + sudo update-alternatives --auto cpp - name: Configure CMake Posix run: | build_type=${{ matrix.build_type }} dir="build_${{ matrix.compiler }}" if [[ "${{ matrix.compiler }}" == "gcc_libstdcxx" ]]; then - c_compiler=gcc-11; cxx_compiler=g++-11 + cxx_compiler=g++-11 else - c_compiler=clang-14; cxx_compiler=clang++-14 + cxx_compiler=clang++-14 fi if [[ "${{ matrix.compiler }}" == "clang_libcxx" ]]; then - options="-DLIBCXX=ON -DSTD=ON -DYACLIB=main" + options="-DLIBCXX=ON" else - options=" -DSTD=ON -DYACLIB=main -DBOOST_THREAD=ON" + options="-DBOOST_THREAD=ON" fi - cmake -S . -B $dir \ - -DCMAKE_BUILD_TYPE=$build_type \ - -DCMAKE_C_COMPILER=${c_compiler} \ - -DCMAKE_CXX_COMPILER=${cxx_compiler} \ - -G"Ninja" \ + cmake -S . -B $dir \ + -DCMAKE_BUILD_TYPE=$build_type \ + -DCMAKE_CXX_COMPILER=${cxx_compiler} \ + -G"Ninja" \ + -DSTD=ON \ + -DYACLIB=v2022.08.23 \ $options - name: Build diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f5aa0c..cca9ba4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,24 +11,21 @@ if (BENCH_SOURCE_DIR STREQUAL BENCH_BINARY_DIR) endif () # Set variables -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_VISIBILITY_PRESET hidden) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS OFF) -set(CMAKE_C_VISIBILITY_PRESET hidden) - set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) message("COMPILE_OPTIONS: ${COMPILE_OPTIONS}") message("LINK_OPTIONS : ${LINK_OPTIONS}") + +include(FetchContent) + if (LIBCXX) - add_compile_options(-stdlib=libc++) + add_compile_options(-stdlib=libc++ -fno-inline) add_link_options(-stdlib=libc++ -lc++abi) endif () @@ -40,7 +37,6 @@ set(UTIL_HEADERS find_package(benchmark QUIET) if (NOT BENCHMARK_FOUND) - include(FetchContent) if (NOT BENCHMARK_FOUND) set(BENCHMARK_ENABLE_TESTING NO) FetchContent_Declare( @@ -52,4 +48,108 @@ if (NOT BENCHMARK_FOUND) endif () endif () -add_subdirectory(future) +function(add_bench BENCH_NAME) + add_executable(${BENCH_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/test/${BENCH_NAME}/${BENCH_NAME}.cpp) + target_link_libraries(${BENCH_NAME} + PRIVATE ${GTEST_BOTH_LIBRARIES} + PRIVATE benchmark::benchmark + PRIVATE ${PROJECT_NAME} + ) + + if (STD) + target_compile_definitions(${BENCH_NAME} PUBLIC STD_ENABLE) + endif () + if (YACLIB) + target_compile_definitions(${BENCH_NAME} PUBLIC YACLIB_ENABLE) + endif () + if (CPPCORO) + target_compile_definitions(${BENCH_NAME} PUBLIC CPPCORO_ENABLE) + endif () + if (FOLLY) + target_compile_definitions(${BENCH_NAME} PUBLIC FOLLY_ENABLE) + endif () + if (ARANGODB) + target_compile_definitions(${BENCH_NAME} PUBLIC ARANGODB_ENABLE) + endif () + if (BOOST_THREAD) + target_compile_definitions(${BENCH_NAME} PUBLIC BOOST_THREAD_ENABLE) + endif () + if (QT) + target_compile_definitions(${BENCH_NAME} PUBLIC QT_ENABLE) + endif () + if (EXPERIMENTAL) + target_compile_definitions(${BENCH_NAME} PUBLIC EXPERIMENTAL_ENABLE) + endif () + + add_test(NAME ${BENCH_NAME} COMMAND ${BENCH_NAME}) +endfunction() + +find_package(Threads REQUIRED) +link_libraries(Threads::Threads) +include_directories(vendor) + +if (YACLIB) # v2022.08.31 + FetchContent_Declare(yaclib + GIT_REPOSITORY https://github.com/YACLib/YACLib.git + GIT_TAG "${YACLIB}" + ) + list(APPEND YACLIB_FLAGS "CORO") + FetchContent_MakeAvailable(yaclib) + link_libraries(yaclib) +endif () +if (FOLLY) + find_package(glog CONFIG REQUIRED) # Ad-hoc + find_package(folly CONFIG REQUIRED) + include_directories(${FOLLY_INCLUDE_DIR}) + link_libraries(Folly::folly) +endif () +if (ARANGODB) + add_library(arangodb STATIC + ${CMAKE_CURRENT_SOURCE_DIR}/vendor/arangodb/futures/Future.cpp + ) +endif () +if (BOOST_THREAD) + find_package(Boost COMPONENTS thread REQUIRED) + link_libraries(Boost::thread) +endif () +if (QT) + find_package(Qt6 COMPONENTS Concurrent REQUIRED) + link_libraries(Qt6::Concurrent) +endif () +if (EXPERIMENTAL) + add_link_options(-lc++experimental) +endif () +if (CPPCORO) + FetchContent_Declare(cppcoro + GIT_REPOSITORY https://github.com/YACLib/cppcoro-for-benchmark.git + GIT_TAG main + ) + FetchContent_MakeAvailable(cppcoro) + link_libraries(cppcoro) +endif () + +macro(add_files) + set(BENCH_HEADERS ${BENCH_HEADERS} PARENT_SCOPE) + set(BENCH_SOURCES ${BENCH_SOURCES} PARENT_SCOPE) +endmacro() + +add_subdirectory(bind) +add_subdirectory(bench) +add_library(${PROJECT_NAME} STATIC) + +target_sources(${PROJECT_NAME} + PRIVATE ${BENCH_SOURCES} + PRIVATE ${BENCH_HEADERS} + ) + +target_include_directories(${PROJECT_NAME} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + ) + +target_link_libraries(${PROJECT_NAME} + PRIVATE ${GTEST_BOTH_LIBRARIES} + PUBLIC benchmark::benchmark + ) + +add_bench(future) +add_bench(coro) diff --git a/README.md b/README.md index d161eea..18960ca 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Results -Future benchmark [results](future/result/RESULTS.md). +Future benchmark [results](test/future/result/RESULTS.md). ## How to add your own results diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt new file mode 100644 index 0000000..8f2ce46 --- /dev/null +++ b/bench/CMakeLists.txt @@ -0,0 +1 @@ +add_files() diff --git a/bench/async_mutex.hpp b/bench/async_mutex.hpp new file mode 100644 index 0000000..1a016a0 --- /dev/null +++ b/bench/async_mutex.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include + +namespace bench { + +template +void AsyncMutex(benchmark::State& state) { + auto* executor = Library::AcquireExecutor(std::thread::hardware_concurrency()); + for (auto _ : state) { + Library::AsyncMutex(executor); + executor->Restart(); + } + Library::ReleaseExecutor(executor); +} + +} // namespace bench diff --git a/bench/collatz.hpp b/bench/collatz.hpp new file mode 100644 index 0000000..b23c48a --- /dev/null +++ b/bench/collatz.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace bench { + +template +void CollatzEager(benchmark::State& state) { + const auto x = state.range(0); + for (auto _ : state) { + Library::CollatzEager(x); + } +} + +template +void CollatzLazy(benchmark::State& state) { + const auto x = state.range(0); + for (auto _ : state) { + Library::CollatzLazy(x); + } +} + +} // namespace bench diff --git a/future/bench/complex.hpp b/bench/complex.hpp similarity index 73% rename from future/bench/complex.hpp rename to bench/complex.hpp index 8e3e539..adfd978 100644 --- a/future/bench/complex.hpp +++ b/bench/complex.hpp @@ -1,15 +1,14 @@ +#pragma once + +#include + #include #include namespace bench { -template -struct Blob { - std::byte buffer[Size]; -}; - -template +template void ComplexBlob(benchmark::State& state) { for (auto _ : state) { if constexpr (N == 0) { diff --git a/future/bench/contention.hpp b/bench/contention.hpp similarity index 96% rename from future/bench/contention.hpp rename to bench/contention.hpp index 73ec120..8154806 100644 --- a/future/bench/contention.hpp +++ b/bench/contention.hpp @@ -1,3 +1,5 @@ +#pragma once + #include namespace bench { diff --git a/future/bench/creation.hpp b/bench/creation.hpp similarity index 70% rename from future/bench/creation.hpp rename to bench/creation.hpp index 3a5e0d5..1e9d216 100644 --- a/future/bench/creation.hpp +++ b/bench/creation.hpp @@ -1,7 +1,16 @@ +#pragma once + #include namespace bench { +template +void ConstantTask(benchmark::State& state) { + for (auto _ : state) { + Library::CreateTask(); + } +} + template void ConstantFuture(benchmark::State& state) { for (auto _ : state) { diff --git a/bench/fibonacci.hpp b/bench/fibonacci.hpp new file mode 100644 index 0000000..26e0f12 --- /dev/null +++ b/bench/fibonacci.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include + +namespace bench { + +template +void Fibonacci(benchmark::State& state) { + const auto n = state.range(0); + auto* executor = Library::AcquireExecutor(std::thread::hardware_concurrency()); + for (auto _ : state) { + Library::Fibonacci(executor, n); + executor->Restart(); + } + Library::ReleaseExecutor(executor); +} + +} // namespace bench diff --git a/bench/latch.hpp b/bench/latch.hpp new file mode 100644 index 0000000..1185f02 --- /dev/null +++ b/bench/latch.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include + +namespace bench { + +template +void Latch(benchmark::State& state) { + const auto tasks_count = state.range(0); + auto* executor = Library::AcquireExecutor(std::thread::hardware_concurrency()); + for (auto _ : state) { + Library::Latch(executor, tasks_count); + executor->Restart(); + } + Library::ReleaseExecutor(executor); +} + +} // namespace bench diff --git a/bench/reschedule.hpp b/bench/reschedule.hpp new file mode 100644 index 0000000..ee1b0e7 --- /dev/null +++ b/bench/reschedule.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace bench { + +template +void Reschedule(benchmark::State& state) { + auto* executor = Library::AcquireExecutor(/*threads*/ 1); + for (auto _ : state) { + Library::Reschedule(executor); + executor->Restart(); + } + Library::ReleaseExecutor(executor); +} + +} // namespace bench diff --git a/bench/thens.hpp b/bench/thens.hpp new file mode 100644 index 0000000..42bbc1e --- /dev/null +++ b/bench/thens.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +namespace bench { + +template +void Then(benchmark::State& state) { + const auto count = state.range(0); + const auto type = state.range(1); + auto* executor = Library::AcquireExecutor(/*threads*/ static_cast(type != 0)); + for (auto _ : state) { + Library::SomeThens(executor, count, type == 2); + if (executor != nullptr) { + executor->Restart(); + } + } + Library::ReleaseExecutor(executor); +} + +} // namespace bench diff --git a/bind/CMakeLists.txt b/bind/CMakeLists.txt new file mode 100644 index 0000000..e3120fe --- /dev/null +++ b/bind/CMakeLists.txt @@ -0,0 +1,30 @@ +list(APPEND BENCH_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/all.hpp + ) + +if (STD) + add_subdirectory(std) +endif () +if (YACLIB) + add_subdirectory(yaclib) +endif () +if (ARANGODB) + add_subdirectory(arangodb) +endif () +if (CPPCORO) + add_subdirectory(cppcoro) +endif () +if (FOLLY) + add_subdirectory(folly) +endif () +if (BOOST_THREAD) + add_subdirectory(boost_thread) +endif () +if (QT) + add_subdirectory(qt) +endif () +if (EXPERIMENTAL) + add_subdirectory(experimental) +endif () + +add_files() diff --git a/bind/all.hpp b/bind/all.hpp new file mode 100644 index 0000000..28f3040 --- /dev/null +++ b/bind/all.hpp @@ -0,0 +1,33 @@ +#pragma once + +#ifdef STD_ENABLE +# include +#endif + +#ifdef YACLIB_ENABLE +# include +#endif + +#ifdef CPPCORO_ENABLE +# include +#endif + +#ifdef FOLLY_ENABLE +# include +#endif + +#ifdef ARANGODB_ENABLE +# include +#endif + +#ifdef BOOST_THREAD_ENABLE +# include +#endif + +#ifdef QT_ENABLE +# include +#endif + +#ifdef EXPERIMENTAL_ENABLE +# include +#endif diff --git a/bind/arangodb/CMakeLists.txt b/bind/arangodb/CMakeLists.txt new file mode 100644 index 0000000..43cc46c --- /dev/null +++ b/bind/arangodb/CMakeLists.txt @@ -0,0 +1,8 @@ +list(APPEND BENCH_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/library.hpp + ) +list(APPEND BENCH_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/library.cpp + ) + +add_files() diff --git a/bind/arangodb/library.cpp b/bind/arangodb/library.cpp new file mode 100644 index 0000000..8203026 --- /dev/null +++ b/bind/arangodb/library.cpp @@ -0,0 +1,285 @@ +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace bench { +namespace { + +template +T Incr(arangodb::futures::Try&& t) { + return std::move(t).get() + 1; +} + +arangodb::futures::Future Thens(arangodb::futures::Future f, std::size_t n, + detail::adb::TestExecutor* executor) { + for (std::size_t i = 0; i != n; ++i) { + if (executor != nullptr) { + arangodb::futures::Promise outer_p; + auto outer_f = outer_p.getFuture(); + std::move(f).then([executor, outer_p = std::move(outer_p)](arangodb::futures::Try&& t) mutable { + executor->add([outer_p = std::move(outer_p), t = std::move(t)]() mutable { + std::move(outer_p).setValue(Incr(std::move(t))); + // TODO possible exception ignored now + }); + }) /*implicit detach*/; + f = std::move(outer_f); + } else { + f = std::move(f).then /*inline*/ (Incr); + } + } + return f; +} + +} // namespace +namespace detail::adb { + +TestExecutor::TestExecutor(std::size_t num_threads) { + num_threads = std::max(std::size_t{1}, num_threads); + _workers.reserve(num_threads); + for (std::size_t i = 0; i != num_threads; ++i) { + _workers.emplace_back([this] { + std::unique_lock lock{_m}; + while (true) { + while (!_jobs.empty()) { + auto work = std::move(_jobs.front()); + _jobs.pop(); + lock.unlock(); + work(); + lock.lock(); + } + if (_stop) { + return; + } + _cv.wait(lock); + } + }); + } +} + +void TestExecutor::Restart() { + std::lock_guard lock{_m}; + _jobs = {}; +} + +void TestExecutor::Join() { + { + std::lock_guard lock{_m}; + _stop = true; + } + _cv.notify_all(); + for (auto& worker : _workers) { + if (worker.joinable()) { + worker.join(); + } + } +} + +TestExecutor::~TestExecutor() { + Join(); +} + +void TestExecutor::add(Job job) { + { + std::lock_guard lock{_m}; + _jobs.push(std::move(job)); + } + _cv.notify_one(); +} + +} // namespace detail::adb + +void ArangoDB::CreateFuture() { + std::ignore = arangodb::futures::makeFuture(42); +} + +void ArangoDB::PromiseAndFuture() { + arangodb::futures::Promise p; + arangodb::futures::Future f = p.getFuture(); + std::move(p).setValue(42); + std::ignore = std::move(f).get(); +} + +detail::adb::TestExecutor* ArangoDB::AcquireExecutor(std::size_t threads) { + if (threads != 0) { + return new detail::adb::TestExecutor{threads}; + } + return nullptr; +} + +void ArangoDB::ReleaseExecutor(detail::adb::TestExecutor* e) { + delete e; +} + +void ArangoDB::SomeThens(detail::adb::TestExecutor* executor, size_t n, bool no_inline) { + const bool is_executor = executor != nullptr; + auto f = arangodb::futures::makeFuture(42); + f = Thens(std::move(f), n, (is_executor && no_inline ? executor : nullptr)); + f = Thens(std::move(f), 1, (is_executor ? executor : nullptr)); + f = Thens(std::move(f), n, (is_executor && no_inline ? executor : nullptr)); + f.wait(); +} + +void ArangoDB::NoContention(benchmark::State& state) { + state.PauseTiming(); + + std::vector> promises(kContentionIteration); + std::vector> futures; + futures.reserve(kContentionIteration); + + std::promise p_producer; + auto f_producer = p_producer.get_future(); + + for (auto& p : promises) { + futures.push_back(p.getFuture().then(Incr)); + } + + std::thread producer{[&] { + p_producer.set_value(); + for (auto& p : promises) { + std::move(p).setValue(42); + } + }}; + + f_producer.wait(); + + state.ResumeTiming(); + + producer.join(); +} + +void ArangoDB::Contention(benchmark::State& state) { + state.PauseTiming(); + + std::vector> promises(kContentionIteration); + std::vector> futures; + futures.reserve(kContentionIteration); + + for (auto& p : promises) { + futures.push_back(p.getFuture()); + } + + BusySemaphoreSPSC semaphore; + std::promise p_consumer; + auto f_consumer = p_consumer.get_future(); + std::promise p_producer; + auto f_producer = p_producer.get_future(); + + auto producer = std::thread([&] { + p_producer.set_value(); + for (auto& p : promises) { + semaphore.Release(); + std::move(p).setValue(42); + } + }); + auto consumer = std::thread([&] { + p_consumer.set_value(); + for (auto& f : futures) { + semaphore.Acquire(); + f = std::move(f).then(Incr); + } + }); + + f_consumer.wait(); + f_producer.wait(); + + state.ResumeTiming(); + + producer.join(); + consumer.join(); +} + +template +void ArangoDB::ComplexBenchmark() { + auto fs = detail::adb::FsGen(); + std::ignore = arangodb::futures::collectAll(fs.begin(), fs.end()).get(); + fs = detail::adb::FsGen(); + std::ignore = arangodb::futures::collectAll /*should be Any, but we don't have it*/ (fs.begin(), fs.end()).get(); + fs = detail::adb::FsGen(); + for (auto& f : fs) { + f = std::move(f).thenValue([](T&& t) { + return std::move(t); + }); + } + fs = detail::adb::FsGen(); + for (auto& f : fs) { + f = std::move(f).thenValue([](T&& t) { + return arangodb::futures::makeFuture(T{std::move(t)}); + }); + } +} + +template void ArangoDB::ComplexBenchmark(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); +template void ArangoDB::ComplexBenchmark>(); + +std::size_t ComputeCollatz(std::size_t x) { + if (x <= 1) { + return 0; + } + if (x % 2 == 0) { + return ComputeCollatz(x / 2) + 1; + } + return ComputeCollatz(3 * x + 1) + 1; +} + +void ArangoDB::Collatz(std::size_t x) { + benchmark::DoNotOptimize(ComputeCollatz(x)); +} + +std::size_t ComputeFibonacci(std::size_t n) { + if (n <= 1) { + return 1; + } + return ComputeFibonacci(n - 2) + ComputeFibonacci(n - 1); +} + +void ArangoDB::Fibonacci(detail::adb::TestExecutor* tp, std::size_t n) { + benchmark::DoNotOptimize(ComputeFibonacci(n)); +} + +constexpr std::size_t kClients = 128; +constexpr std::size_t kQperClient = 100; + +void MutexWork(std::latch& latch, std::mutex& m, std::size_t& shared_id) { + for (std::size_t i = 0; i < kQperClient; ++i) { + m.lock(); + ++shared_id; + m.unlock(); + std::this_thread::sleep_for(std::chrono::nanoseconds{10}); + } + latch.count_down(); +} + +void ArangoDB::AsyncMutex(detail::adb::TestExecutor* tp) { + std::latch latch{kClients}; + std::mutex m; + std::size_t shared_id = 0; + for (std::size_t i = 0; i < kClients; ++i) { + tp->add([&] { + MutexWork(latch, m, shared_id); + }); + } + latch.wait(); +} + +} // namespace bench diff --git a/bind/arangodb/library.hpp b/bind/arangodb/library.hpp new file mode 100644 index 0000000..c9e8449 --- /dev/null +++ b/bind/arangodb/library.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace bench { +namespace detail::adb { + +class TestExecutor final { + public: + explicit TestExecutor(std::size_t num_threads); + + void Restart(); + void Join(); + + ~TestExecutor(); + + using Job = fu2::unique_function; + void add(Job job); + + private: + std::mutex _m; + std::condition_variable _cv; + std::queue _jobs; + std::vector _workers; + bool _stop = false; +}; + +template +arangodb::futures::Future FGen() { + arangodb::futures::Promise p; + auto f = p.getFuture(); + f = std::move(f) + .thenValue([](T&& t) { + return std::move(t); + }) + .thenValue([](T&& t) { + return arangodb::futures::makeFuture(std::move(t)); + }) + .thenValue([](T&& t) { + return std::move(t); + }) + .thenValue([](T&& t) { + return arangodb::futures::makeFuture(std::move(t)); + }); + p.setValue(T{}); + return f; +} + +template +std::vector> FsGen() { + std::vector> fs; + fs.reserve(Size); + for (std::size_t i = 0; i < Size; ++i) { + fs.emplace_back(FGen()); + } + return fs; +} + +} // namespace detail::adb + +struct ArangoDB { + static void CreateFuture(); + static void PromiseAndFuture(); + + static detail::adb::TestExecutor* AcquireExecutor(std::size_t threads); + static void SomeThens(detail::adb::TestExecutor* executor, size_t n, bool no_inline); + static void ReleaseExecutor(detail::adb::TestExecutor* e); + + template + static void ComplexBenchmark(); + + static constexpr std::size_t kContentionIteration = 10000; + static void NoContention(benchmark::State& state); + static void Contention(benchmark::State& state); + + static void Collatz(std::size_t x); + static void Fibonacci(detail::adb::TestExecutor* tp, std::size_t n); + static void AsyncMutex(detail::adb::TestExecutor* tp); +}; + +} // namespace bench diff --git a/bind/boost_thread/CMakeLists.txt b/bind/boost_thread/CMakeLists.txt new file mode 100644 index 0000000..43cc46c --- /dev/null +++ b/bind/boost_thread/CMakeLists.txt @@ -0,0 +1,8 @@ +list(APPEND BENCH_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/library.hpp + ) +list(APPEND BENCH_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/library.cpp + ) + +add_files() diff --git a/future/bind/boost_thread/future.cpp b/bind/boost_thread/library.cpp similarity index 100% rename from future/bind/boost_thread/future.cpp rename to bind/boost_thread/library.cpp diff --git a/future/bind/boost_thread/future.hpp b/bind/boost_thread/library.hpp similarity index 100% rename from future/bind/boost_thread/future.hpp rename to bind/boost_thread/library.hpp diff --git a/bind/cppcoro/CMakeLists.txt b/bind/cppcoro/CMakeLists.txt new file mode 100644 index 0000000..43cc46c --- /dev/null +++ b/bind/cppcoro/CMakeLists.txt @@ -0,0 +1,8 @@ +list(APPEND BENCH_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/library.hpp + ) +list(APPEND BENCH_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/library.cpp + ) + +add_files() diff --git a/bind/cppcoro/library.cpp b/bind/cppcoro/library.cpp new file mode 100644 index 0000000..17dfedb --- /dev/null +++ b/bind/cppcoro/library.cpp @@ -0,0 +1,185 @@ +#include "cppcoro/async_mutex.hpp" +#include "cppcoro/schedule_on.hpp" +#include "cppcoro/when_all.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +namespace bench { +namespace { + +cppcoro::task<> ComputeCollatz(std::size_t x) { + if (x <= 1) { + co_return; + } + if (x % 2 == 0) { + co_await ComputeCollatz(x / 2); + co_return; + } + co_await ComputeCollatz(3 * x + 1); + co_return; +} + +cppcoro::task ComputeFibonacciAsync(detail::co::TestExecutor& e, std::size_t n) { + if (n <= 1) { + co_return 1; + } + // same results + // co_await e.schedule(); + // auto f0 = ComputeFibonacciAsync(e, n - 1); + // auto f1 = ComputeFibonacciAsync(e, n - 2); + auto f0 = ComputeFibonacciAsync(e, n - 1) | cppcoro::schedule_on(e); + auto f1 = ComputeFibonacciAsync(e, n - 2); + auto [r0, r1] = co_await cppcoro::when_all(std::move(f0), std::move(f1)); + co_return r0 + r1; +} + +cppcoro::task LatchDone(detail::co::TestExecutor& tp, cppcoro::async_latch& wg) { + co_await tp.schedule(); + wg.count_down(); +} + +cppcoro::task LatchAwait(detail::co::TestExecutor& tp, cppcoro::async_latch& wg) { + co_await tp.schedule(); + co_await wg; +} + +constexpr std::size_t kClients = 128; +constexpr std::size_t kQperClient = 100; + +cppcoro::task MutexWork(detail::co::TestExecutor& tp, auto& m, std::size_t& shared_id) { + co_await tp.schedule(); + for (std::size_t i = 0; i < kQperClient; ++i) { + co_await m.lock_async(); + ++shared_id; + m.unlock(); + std::this_thread::sleep_for(std::chrono::nanoseconds{10}); + } +} + +cppcoro::task Reschedule100(detail::co::TestExecutor& tp) { + for (int i = 0; i < 100; ++i) { + co_await tp.schedule(); + } +} + +} // namespace +namespace detail::co { + +TestExecutor::TestExecutor(std::size_t num_threads) { + num_threads = std::max(std::size_t{1}, num_threads); + _workers.reserve(num_threads); + for (std::size_t i = 0; i != num_threads; ++i) { + _workers.emplace_back([this] { + std::unique_lock lock{_m}; + while (true) { + while (!_jobs.Empty()) { + auto& job = _jobs.PopFront(); + lock.unlock(); + static_cast(job)._awaitingCoroutine.resume(); + lock.lock(); + } + if (_stop) { + return; + } + _cv.wait(lock); + } + }); + } +} + +void TestExecutor::Restart() { + std::lock_guard lock{_m}; +} + +void TestExecutor::Join() { + { + std::lock_guard lock{_m}; + _stop = true; + } + _cv.notify_all(); + for (auto& worker : _workers) { + if (worker.joinable()) { + worker.join(); + } + } +} + +TestExecutor::~TestExecutor() { + Join(); +} + +void TestExecutor::Submit(TestExecutor::schedule_operation* job) noexcept { + { + std::lock_guard guard{_m}; + _jobs.PushBack(*job); + } + _cv.notify_one(); +} + +} // namespace detail::co + +detail::co::TestExecutor* CppCoro::AcquireExecutor(std::size_t threads) { + if (threads != 0) { + return new detail::co::TestExecutor{threads}; + } + return nullptr; +} + +void CppCoro::ReleaseExecutor(detail::co::TestExecutor* e) { + delete e; +} + +void CppCoro::CollatzLazy(std::size_t x) { + auto task = ComputeCollatz(x); + cppcoro::sync_wait(std::move(task)); +} + +void CppCoro::Fibonacci(detail::co::TestExecutor* tp, std::size_t n) { + auto task = ComputeFibonacciAsync(*tp, n); + std::ignore = cppcoro::sync_wait(std::move(task)); +} + +void CppCoro::AsyncMutex(detail::co::TestExecutor* tp) { + cppcoro::async_mutex m; + std::size_t shared_id = 0; + auto all = [&]() -> cppcoro::task { + std::vector> tasks; + tasks.reserve(kClients); + for (std::size_t i = 0; i < kClients; ++i) { + tasks.push_back(MutexWork(*tp, m, shared_id)); + } + co_await cppcoro::when_all_ready(std::move(tasks)); + }; + cppcoro::sync_wait(all()); +} + +void CppCoro::Latch(detail::co::TestExecutor* tp, std::size_t tasks_count) { + cppcoro::async_latch wg{static_cast(tasks_count)}; + auto all = [&]() -> cppcoro::task { + std::vector> tasks; + tasks.reserve(tasks_count * 2); + for (std::size_t i = 0; i < tasks_count * 2; ++i) { + if (i % 2 == 0) { + tasks.push_back(LatchAwait(*tp, wg)); + } else { + tasks.push_back(LatchDone(*tp, wg)); + } + } + co_await cppcoro::when_all_ready(std::move(tasks)); + }; + cppcoro::sync_wait(all()); +} + +void CppCoro::Reschedule(detail::co::TestExecutor* tp) { + auto task = Reschedule100(*tp); + cppcoro::sync_wait(std::move(task)); +} + +} // namespace bench diff --git a/bind/cppcoro/library.hpp b/bind/cppcoro/library.hpp new file mode 100644 index 0000000..39efeea --- /dev/null +++ b/bind/cppcoro/library.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace bench { +namespace detail::co { + +class TestExecutor { + public: + explicit TestExecutor(std::size_t num_threads); + + ~TestExecutor(); + + struct schedule_operation : yaclib::detail::Node { + schedule_operation(TestExecutor* tp) noexcept : _tp{tp} { + } + + bool await_ready() noexcept { + return false; + } + + void await_suspend(cppcoro::coroutine_handle<> awaitingCoroutine) noexcept { + _awaitingCoroutine = awaitingCoroutine; + _tp->Submit(this); + } + + void await_resume() noexcept { + } + + TestExecutor* _tp; + cppcoro::coroutine_handle<> _awaitingCoroutine; + }; + + [[nodiscard]] schedule_operation schedule() noexcept { + return schedule_operation{this}; + } + + void Restart(); + void Join(); + + private: + friend class schedule_operation; + + void Submit(schedule_operation* job) noexcept; + + std::mutex _m; + std::condition_variable _cv; + detail::yb::List _jobs; + std::vector _workers; + bool _stop = false; +}; + +} // namespace detail::co + +struct CppCoro { + static detail::co::TestExecutor* AcquireExecutor(std::size_t threads); + static void ReleaseExecutor(detail::co::TestExecutor* e); + + static void CollatzLazy(std::size_t x); + static void Fibonacci(detail::co::TestExecutor* tp, std::size_t n); + static void AsyncMutex(detail::co::TestExecutor* tp); + static void Latch(detail::co::TestExecutor* tp, std::size_t task_count); + static void Reschedule(detail::co::TestExecutor* tp); +}; + +} // namespace bench diff --git a/bind/experimental/CMakeLists.txt b/bind/experimental/CMakeLists.txt new file mode 100644 index 0000000..43cc46c --- /dev/null +++ b/bind/experimental/CMakeLists.txt @@ -0,0 +1,8 @@ +list(APPEND BENCH_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/library.hpp + ) +list(APPEND BENCH_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/library.cpp + ) + +add_files() diff --git a/future/bind/experimental/future.cpp b/bind/experimental/library.cpp similarity index 100% rename from future/bind/experimental/future.cpp rename to bind/experimental/library.cpp diff --git a/future/bind/experimental/future.hpp b/bind/experimental/library.hpp similarity index 100% rename from future/bind/experimental/future.hpp rename to bind/experimental/library.hpp diff --git a/bind/folly/CMakeLists.txt b/bind/folly/CMakeLists.txt new file mode 100644 index 0000000..43cc46c --- /dev/null +++ b/bind/folly/CMakeLists.txt @@ -0,0 +1,8 @@ +list(APPEND BENCH_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/library.hpp + ) +list(APPEND BENCH_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/library.cpp + ) + +add_files() diff --git a/bind/folly/library.cpp b/bind/folly/library.cpp new file mode 100644 index 0000000..d7179d6 --- /dev/null +++ b/bind/folly/library.cpp @@ -0,0 +1,277 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace bench { +namespace { + +template +T Incr(folly::Try&& t) { + return std::move(t).value() + 1; +} + +folly::Future Thens(folly::Future f, std::size_t n, bool is_executor) { + for (std::size_t i = 0; i != n; ++i) { + if (is_executor) { + f = std::move(f).then(Incr); + } else { + f = std::move(f).thenInline(Incr); + } + } + return f; +} + +folly::coro::Task<> ComputeCollatzLazy(std::size_t x) { + if (x <= 1) { + co_return; + } + if (x % 2 == 0) { + auto foo = ComputeCollatzLazy(x / 2); + co_await std::move(foo); + co_return; + } + auto foo = ComputeCollatzLazy(3 * x + 1); + co_await std::move(foo); + co_return; +} + +folly::coro::Task ComputeFibonacciAsync(detail::fy::TestExecutor* e, std::size_t n) { + if (n <= 1) { + co_return 1; + } + auto f0 = ComputeFibonacciAsync(e, n - 1).scheduleOn(e); + auto f1 = ComputeFibonacciAsync(e, n - 2); + auto [r0, r1] = co_await folly::coro::collectAll(std::move(f0), std::move(f1)); + co_return r0 + r1; +} + +folly::coro::Task<> Reschedule100() { + for (size_t i = 0; i != 100; ++i) { + co_await folly::coro::co_reschedule_on_current_executor; + } +} + +} // namespace +namespace detail::fy { + +TestExecutor::TestExecutor(std::size_t num_threads) { + num_threads = std::max(std::size_t{1}, num_threads); + _workers.reserve(num_threads); + for (std::size_t i = 0; i != num_threads; ++i) { + _workers.emplace_back([this] { + std::unique_lock lock{_m}; + while (true) { + while (!_tasks.empty()) { + auto work = std::move(_tasks.front()); + _tasks.pop(); + lock.unlock(); + work(); + lock.lock(); + } + if (_stop) { + return; + } + _cv.wait(lock); + } + }); + } +} + +void TestExecutor::Restart() { + std::lock_guard lock{_m}; + _tasks = {}; +} + +void TestExecutor::Join() { + { + std::lock_guard lock{_m}; + _stop = true; + } + _cv.notify_all(); + for (auto& worker : _workers) { + if (worker.joinable()) { + worker.join(); + } + } +} + +TestExecutor::~TestExecutor() { + Join(); +} + +void TestExecutor::add(folly::Func f) { + { + std::lock_guard lock{_m}; + _tasks.push(std::move(f)); + } + _cv.notify_one(); +} + +} // namespace detail::fy + +void Folly::CreateFuture() { + std::ignore = folly::makeFuture(42); +} + +void Folly::PromiseAndFuture() { + folly::Promise p; + folly::Future f = p.getFuture(); + std::move(p).setValue(42); + std::ignore = std::move(f).get(); +} + +detail::fy::TestExecutor* Folly::AcquireExecutor(std::size_t threads) { + if (threads != 0) { + return new detail::fy::TestExecutor{threads}; + } + return nullptr; +} + +void Folly::ReleaseExecutor(detail::fy::TestExecutor* e) { + delete e; +} + +void Folly::SomeThens(detail::fy::TestExecutor* executor, size_t n, bool no_inline) { + const bool is_executor = executor != nullptr; + auto f = folly::makeFuture(42).via(executor); + f = Thens(std::move(f), n, is_executor && no_inline); + f = Thens(std::move(f), 1, is_executor); + f = Thens(std::move(f), n, is_executor && no_inline); + f.wait(); +} + +void Folly::NoContention(benchmark::State& state, std::size_t iterations) { + state.PauseTiming(); + + std::vector> promises(iterations); + std::vector> futures; + futures.reserve(iterations); + + std::promise p_producer; + auto f_producer = p_producer.get_future(); + + for (auto& p : promises) { + futures.push_back(p.getFuture().thenInline(Incr)); + } + + std::thread producer{[&] { + p_producer.set_value(); + for (auto& p : promises) { + std::move(p).setValue(42); + } + }}; + + f_producer.wait(); + + state.ResumeTiming(); + + producer.join(); +} + +void Folly::Contention(benchmark::State& state, std::size_t iterations) { + state.PauseTiming(); + + std::vector> promises(iterations); + std::vector> futures; + futures.reserve(iterations); + + for (auto& p : promises) { + futures.push_back(p.getFuture()); + } + + BusySemaphoreSPSC semaphore; + std::promise p_consumer; + auto f_consumer = p_consumer.get_future(); + std::promise p_producer; + auto f_producer = p_producer.get_future(); + + auto producer = std::thread([&] { + p_producer.set_value(); + for (auto& p : promises) { + semaphore.Release(); + std::move(p).setValue(42); + } + }); + auto consumer = std::thread([&] { + p_consumer.set_value(); + for (auto& f : futures) { + semaphore.Acquire(); + f = std::move(f).thenInline(Incr); + } + }); + + f_consumer.wait(); + f_producer.wait(); + + state.ResumeTiming(); + + producer.join(); + consumer.join(); +} + +template +void Folly::ComplexBenchmark() { + auto fs = detail::fy::FsGen(); + std::ignore = folly::collectAll(fs.begin(), fs.end()).value(); + fs = detail::fy::FsGen(); + std::ignore = folly::collectAny(fs.begin(), fs.end()).value(); + fs = detail::fy::FsGen(); + for (auto& f : fs) { + f = std::move(f).thenValueInline([](T&& t) { + return std::move(t); + }); + } + fs = detail::fy::FsGen(); + for (auto& f : fs) { + f = std::move(f).thenValueInline([](T&& t) { + return folly::makeFuture(T{std::move(t)}); + }); + } +} + +template void Folly::ComplexBenchmark(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); +template void Folly::ComplexBenchmark>(); + +void Folly::CollatzLazy(std::size_t x) { + auto task = ComputeCollatzLazy(x); + folly::coro::blockingWait(std::move(task)); +} + +void Folly::Fibonacci(detail::fy::TestExecutor* tp, std::size_t n) { + auto task = ComputeFibonacciAsync(tp, n); + std::ignore = folly::coro::blockingWait(std::move(task)); +} + +void Folly::AsyncMutex(detail::fy::TestExecutor* tp) { +} +void Folly::Latch(detail::fy::TestExecutor* tp, std::size_t task_count) { +} + +void Folly::Reschedule(detail::fy::TestExecutor* tp) { + auto task = Reschedule100().scheduleOn(tp); + folly::coro::blockingWait(std::move(task)); +} + +} // namespace bench diff --git a/future/bind/folly/future.hpp b/bind/folly/library.hpp similarity index 63% rename from future/bind/folly/future.hpp rename to bind/folly/library.hpp index 1f03725..1c4e9a8 100644 --- a/future/bind/folly/future.hpp +++ b/bind/folly/library.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -65,35 +67,22 @@ struct Folly { static void CreateFuture(); static void PromiseAndFuture(); - using Executor = detail::fy::TestExecutor; - static void SomeThens(Executor* executor, size_t n, bool no_inline); + static detail::fy::TestExecutor* AcquireExecutor(std::size_t threads); + static void ReleaseExecutor(detail::fy::TestExecutor* e); + + static void SomeThens(detail::fy::TestExecutor* executor, size_t n, bool no_inline); template static void ComplexBenchmark(); - static constexpr std::size_t kContentionIteration = 10000; - static void NoContention(benchmark::State& state); - static void Contention(benchmark::State& state); -}; + static void NoContention(benchmark::State& state, std::size_t iterations = 10'000); + static void Contention(benchmark::State& state, std::size_t iterations = 10'000); -template -void Folly::ComplexBenchmark() { - auto fs = detail::fy::FsGen(); - std::ignore = folly::collectAll(fs.begin(), fs.end()).value(); - fs = detail::fy::FsGen(); - std::ignore = folly::collectAny(fs.begin(), fs.end()).value(); - fs = detail::fy::FsGen(); - for (auto& f : fs) { - f = std::move(f).thenValueInline([](T&& t) { - return std::move(t); - }); - } - fs = detail::fy::FsGen(); - for (auto& f : fs) { - f = std::move(f).thenValueInline([](T&& t) { - return folly::makeFuture(T{std::move(t)}); - }); - } -} + static void CollatzLazy(std::size_t x); + static void Fibonacci(detail::fy::TestExecutor* tp, std::size_t n); + static void AsyncMutex(detail::fy::TestExecutor* tp); + static void Latch(detail::fy::TestExecutor* tp, std::size_t task_count); + static void Reschedule(detail::fy::TestExecutor* tp); +}; } // namespace bench diff --git a/bind/qt/CMakeLists.txt b/bind/qt/CMakeLists.txt new file mode 100644 index 0000000..43cc46c --- /dev/null +++ b/bind/qt/CMakeLists.txt @@ -0,0 +1,8 @@ +list(APPEND BENCH_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/library.hpp + ) +list(APPEND BENCH_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/library.cpp + ) + +add_files() diff --git a/future/bind/qt/future.cpp b/bind/qt/library.cpp similarity index 100% rename from future/bind/qt/future.cpp rename to bind/qt/library.cpp diff --git a/future/bind/qt/future.hpp b/bind/qt/library.hpp similarity index 100% rename from future/bind/qt/future.hpp rename to bind/qt/library.hpp diff --git a/future/bind/std/CMakeLists.txt b/bind/std/CMakeLists.txt similarity index 63% rename from future/bind/std/CMakeLists.txt rename to bind/std/CMakeLists.txt index 4c4c849..c9645a3 100644 --- a/future/bind/std/CMakeLists.txt +++ b/bind/std/CMakeLists.txt @@ -1,5 +1,5 @@ list(APPEND BENCH_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/future.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/library.hpp ) list(APPEND BENCH_SOURCES ) diff --git a/future/bind/std/future.hpp b/bind/std/library.hpp similarity index 70% rename from future/bind/std/future.hpp rename to bind/std/library.hpp index b8177a0..f330e75 100644 --- a/future/bind/std/future.hpp +++ b/bind/std/library.hpp @@ -17,6 +17,12 @@ struct Std { std::move(p).set_value(42); std::ignore = std::move(f).get(); } + static void Mutex() { + std::promise p; + std::future f = p.get_future(); + std::move(p).set_value(42); + std::ignore = std::move(f).get(); + } }; } // namespace bench diff --git a/future/bind/yaclib/CMakeLists.txt b/bind/yaclib/CMakeLists.txt similarity index 59% rename from future/bind/yaclib/CMakeLists.txt rename to bind/yaclib/CMakeLists.txt index aa4b861..42fa4f0 100644 --- a/future/bind/yaclib/CMakeLists.txt +++ b/bind/yaclib/CMakeLists.txt @@ -1,10 +1,10 @@ list(APPEND BENCH_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/future.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/library.hpp ${CMAKE_CURRENT_SOURCE_DIR}/intrusive_list.hpp ) list(APPEND BENCH_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/future.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/library.cpp ) add_files() diff --git a/future/bind/yaclib/intrusive_list.hpp b/bind/yaclib/intrusive_list.hpp similarity index 100% rename from future/bind/yaclib/intrusive_list.hpp rename to bind/yaclib/intrusive_list.hpp diff --git a/bind/yaclib/library.cpp b/bind/yaclib/library.cpp new file mode 100644 index 0000000..3815dd3 --- /dev/null +++ b/bind/yaclib/library.cpp @@ -0,0 +1,375 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace bench { +namespace { + +template +T Incr(yaclib::Result&& r) { + return std::move(r).Ok() + 1; +} + +yaclib::FutureOn Thens(yaclib::FutureOn f, std::size_t n, bool is_executor) { + for (std::size_t i = 0; i != n; ++i) { + if (is_executor) { + f = std::move(f).Then(Incr); + } else { + f = std::move(f).ThenInline(Incr); + } + } + return f; +} + +yaclib::Future<> Reschedule100(yaclib::IExecutor& tp) { + for (int i = 0; i < 100; ++i) { + co_await On(tp); + } + co_return{}; +} + +yaclib::Future<> LatchAwait(yaclib::IExecutor& tp, yaclib::WaitGroup<>& wg) { + co_await On(tp); + co_await wg; + co_return{}; +} + +yaclib::Future<> LatchDone(yaclib::IExecutor& tp, yaclib::WaitGroup<>& wg) { + co_await On(tp); + wg.Done(); + co_return{}; +} + +constexpr std::size_t kClients = 128; +constexpr std::size_t kQperClient = 100; + +yaclib::Future<> MutexWork(yaclib::IExecutor& tp, auto& m, std::size_t& shared_id) { + co_await On(tp); + for (std::size_t i = 0; i < kQperClient; ++i) { + co_await m.Lock(); + ++shared_id; + co_await m.Unlock(tp); + std::this_thread::sleep_for(std::chrono::nanoseconds{10}); + } + co_return{}; +} + +yaclib::Future<> ComputeCollatzEager(std::size_t x) { + if (x <= 1) { + co_return{}; + } + if (x % 2 == 0) { + auto foo = ComputeCollatzEager(x / 2); + co_await Await(foo); + co_return{}; + } + auto foo = ComputeCollatzEager(3 * x + 1); + co_await Await(foo); + co_return{}; +} + +/*yaclib::Task<> ComputeCollatzLazy(std::size_t x) { + if (x <= 1) { + co_return{}; + } + if (x % 2 == 0) { + auto foo = ComputeCollatzLazy(x / 2); + co_await Await(foo); + co_return{}; + } + auto foo = ComputeCollatzLazy(3 * x + 1); + co_await Await(foo); + co_return{}; +}*/ + +yaclib::Future ComputeFibonacciAsync(yaclib::IExecutor& e, std::size_t n); + +yaclib::Future ComputeFibonacciSync(yaclib::IExecutor& e, std::size_t n) { + if (n <= 1) { + co_return 1; + } + auto f1 = ComputeFibonacciAsync(e, n - 1); + auto f2 = ComputeFibonacciSync(e, n - 2); + co_await Await(f1, f2); + co_return std::move(f1).Touch().Ok() + std::move(f2).Touch().Ok(); +} + +yaclib::Future ComputeFibonacciAsync(yaclib::IExecutor& e, std::size_t n) { + if (n <= 1) { + co_return 1; + } + co_await On(e); + auto f1 = ComputeFibonacciAsync(e, n - 1); + auto f2 = ComputeFibonacciSync(e, n - 2); + co_await Await(f1, f2); + co_return std::move(f1).Touch().Ok() + std::move(f2).Touch().Ok(); +} + +} // namespace +namespace detail::yb { + +TestExecutor::TestExecutor(std::size_t num_threads) { + num_threads = std::max(std::size_t{1}, num_threads); + _workers.reserve(num_threads); + for (std::size_t i = 0; i != num_threads; ++i) { + _workers.emplace_back([this] { + std::unique_lock lock{_m}; + while (true) { + while (!_jobs.Empty()) { + auto& job = _jobs.PopFront(); + lock.unlock(); + static_cast(job).Call(); + lock.lock(); + } + if (_stop) { + return; + } + _cv.wait(lock); + } + }); + } +} + +void TestExecutor::Restart() { + std::lock_guard lock{_m}; +} + +void TestExecutor::Join() { + { + std::lock_guard lock{_m}; + _stop = true; + } + _cv.notify_all(); + for (auto& worker : _workers) { + if (worker.joinable()) { + worker.join(); + } + } +} + +TestExecutor::~TestExecutor() { + Join(); +} + +yaclib::IExecutor::Type TestExecutor::Tag() const noexcept { + return Type::Custom; +} + +void TestExecutor::Submit(yaclib::Job& job) noexcept { + { + std::lock_guard guard{_m}; + _jobs.PushBack(job); + } + _cv.notify_one(); +} + +} // namespace detail::yb + +void YACLib::CreateFuture() { + std::ignore = yaclib::MakeFuture(42); +} + +void YACLib::PromiseAndFuture() { + auto [f, p] = yaclib::MakeContract(); + std::move(p).Set(42); + std::ignore = std::move(f).Get().Ok(); +} + +detail::yb::TestExecutor* YACLib::AcquireExecutor(std::size_t threads) { + if (threads != 0) { + return new detail::yb::TestExecutor{threads}; + } + return nullptr; +} + +void YACLib::ReleaseExecutor(detail::yb::TestExecutor* e) { + delete e; +} + +void YACLib::SomeThens(detail::yb::TestExecutor* executor, size_t n, bool no_inline) { + bool is_executor = executor != nullptr; + auto f = yaclib::MakeFuture(42).On(executor != nullptr ? *executor : yaclib::MakeInline()); + f = Thens(std::move(f), n, is_executor && no_inline); + f = Thens(std::move(f), 1, is_executor); + f = Thens(std::move(f), n, is_executor && no_inline); + Wait(f); +} + +void YACLib::NoContention(benchmark::State& state, size_t iterations) { + state.PauseTiming(); + + std::vector> promises; + std::vector> futures; + promises.reserve(iterations); + futures.reserve(iterations); + + std::promise p_producer; + auto f_producer = p_producer.get_future(); + + for (std::size_t i = 0; i != iterations; ++i) { + auto [f, p] = yaclib::MakeContract(); + promises.emplace_back(std::move(p)); + futures.emplace_back(std::move(f).ThenInline(Incr)); + } + + auto producer = std::thread([&]() mutable { + p_producer.set_value(); + + for (auto& p : promises) { + std::move(p).Set(42); + } + }); + + f_producer.wait(); + + state.ResumeTiming(); + + producer.join(); +} + +void YACLib::Contention(benchmark::State& state, size_t iterations) { + state.PauseTiming(); + + std::vector> promises; + std::vector> futures; + promises.reserve(iterations); + futures.reserve(iterations); + + for (std::size_t i = 0; i != iterations; ++i) { + auto [f, p] = yaclib::MakeContract(); + futures.push_back(std::move(f)); + promises.push_back(std::move(p)); + } + + BusySemaphoreSPSC semaphore; + std::promise p_consumer; + auto f_consumer = p_consumer.get_future(); + std::promise p_producer; + auto f_producer = p_producer.get_future(); + + auto producer = std::thread([&] { + p_producer.set_value(); + for (auto& p : promises) { + semaphore.Release(); + std::move(p).Set(42); + } + }); + auto consumer = std::thread([&] { + p_consumer.set_value(); + for (auto& f : futures) { + semaphore.Acquire(); + f = std::move(f).ThenInline(Incr); + } + }); + + f_consumer.wait(); + f_producer.wait(); + + state.ResumeTiming(); + + producer.join(); + consumer.join(); +} + +template +void YACLib::ComplexBenchmark() { + auto fs = detail::yb::FsGen(); + (void)WhenAll(fs.begin(), fs.end()).Get().Ok(); + fs = detail::yb::FsGen(); + (void)WhenAny(fs.begin(), fs.end()).Get().Ok(); + fs = detail::yb::FsGen(); + for (auto& f : fs) { + if constexpr (std::is_void_v) { + f = std::move(f).ThenInline([] { + }); + } else { + f = std::move(f).ThenInline([](T&& t) { + return std::move(t); + }); + } + } + fs = detail::yb::FsGen(); + for (auto& f : fs) { + if constexpr (std::is_void_v) { + f = std::move(f).ThenInline([](yaclib::Result&& /*TODO(MBkkt) remove this*/) { + return yaclib::MakeFuture(); + }); + } else { + f = std::move(f).ThenInline([](T&& t) { + return yaclib::MakeFuture(std::move(t)); + }); + } + } +} + +template void YACLib::ComplexBenchmark(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); +template void YACLib::ComplexBenchmark>(); + +void YACLib::CollatzEager(std::size_t x) { + auto future = ComputeCollatzEager(x); + std::ignore = std::move(future).Get().Ok(); +} + +void YACLib::Fibonacci(detail::yb::TestExecutor* tp, std::size_t n) { + auto future = ComputeFibonacciAsync(*tp, n); + std::ignore = std::move(future).Get().Ok(); +} + +void YACLib::AsyncMutex(detail::yb::TestExecutor* tp) { + yaclib::AsyncMutex::kAll> m; + std::size_t shared_id = 0; + yaclib::WaitGroup<> wg{kClients}; + for (std::size_t i = 0; i < kClients; ++i) { + wg.Consume(MutexWork(*tp, m, shared_id)); + } + wg.Wait(); +} + +void YACLib::Latch(detail::yb::TestExecutor* tp, std::size_t tasks_count) { + yaclib::WaitGroup<> wg; + wg.Add(tasks_count); + auto all = [&]() -> yaclib::Future<> { + for (std::size_t i = 0; i < tasks_count * 2; ++i) { + if (i % 2 == 0) { + LatchAwait(*tp, wg).Detach(); + } else { + LatchDone(*tp, wg).Detach(); + } + } + co_await wg; + co_return{}; + }; + std::ignore = all().Get(); +} + +void YACLib::Reschedule(detail::yb::TestExecutor* tp) { + auto task = Reschedule100(*tp); + std::ignore = std::move(task).Get().Ok(); +} + +} // namespace bench diff --git a/future/bind/yaclib/future.hpp b/bind/yaclib/library.hpp similarity index 56% rename from future/bind/yaclib/future.hpp rename to bind/yaclib/library.hpp index dde733a..2e2f72b 100644 --- a/future/bind/yaclib/future.hpp +++ b/bind/yaclib/library.hpp @@ -1,11 +1,13 @@ #pragma once #include -#include -#include +#include #include #include -#include +#include +#include +#include +#include #include #include @@ -32,13 +34,13 @@ class TestExecutor final : public yaclib::IExecutor { void DecRef() noexcept final { } - [[nodiscard]] Type Tag() const final; + [[nodiscard]] Type Tag() const noexcept final; - bool Submit(yaclib::ITask& task) noexcept final; + void Submit(yaclib::Job& job) noexcept final; std::mutex _m; std::condition_variable _cv; - List _tasks; + List _jobs; std::vector _workers; bool _stop = false; }; @@ -50,12 +52,12 @@ yaclib::Future FGen() { f = std::move(f) .ThenInline([] { }) - .ThenInline([](yaclib::Result&& /*TODO(MBkkt) remove this*/) { + .ThenInline([] { return yaclib::MakeFuture(); }) .ThenInline([] { }) - .ThenInline([](yaclib::Result&& /*TODO(MBkkt) remove this*/) { + .ThenInline([] { return yaclib::MakeFuture(); }); std::move(p).Set(); @@ -94,46 +96,22 @@ struct YACLib { static void CreateFuture(); static void PromiseAndFuture(); - using Executor = detail::yb::TestExecutor; - static void SomeThens(Executor* executor, size_t n, bool no_inline); + static detail::yb::TestExecutor* AcquireExecutor(std::size_t threads); + static void ReleaseExecutor(detail::yb::TestExecutor* e); + + static void SomeThens(detail::yb::TestExecutor* executor, size_t n, bool no_inline); template static void ComplexBenchmark(); - static constexpr std::size_t kContentionIteration = 10000; - static void NoContention(benchmark::State& state); - static void Contention(benchmark::State& state); -}; + static void NoContention(benchmark::State& state, std::size_t iterations = 10'000); + static void Contention(benchmark::State& state, std::size_t iterations = 10'000); -template -void YACLib::ComplexBenchmark() { - auto fs = detail::yb::FsGen(); - (void)WhenAll(fs.begin(), fs.end()).Get().Ok(); - fs = detail::yb::FsGen(); - (void)WhenAny(fs.begin(), fs.end()).Get().Ok(); - fs = detail::yb::FsGen(); - for (auto& f : fs) { - if constexpr (std::is_void_v) { - f = std::move(f).ThenInline([] { - }); - } else { - f = std::move(f).ThenInline([](T&& t) { - return std::move(t); - }); - } - } - fs = detail::yb::FsGen(); - for (auto& f : fs) { - if constexpr (std::is_void_v) { - f = std::move(f).ThenInline([](yaclib::Result&& /*TODO(MBkkt) remove this*/) { - return yaclib::MakeFuture(); - }); - } else { - f = std::move(f).ThenInline([](T&& t) { - return yaclib::MakeFuture(std::move(t)); - }); - } - } -} + static void CollatzEager(std::size_t x); + static void Fibonacci(detail::yb::TestExecutor* tp, size_t n); + static void AsyncMutex(detail::yb::TestExecutor* tp); + static void Latch(detail::yb::TestExecutor* tp, std::size_t task_count); + static void Reschedule(detail::yb::TestExecutor* tp); +}; } // namespace bench diff --git a/future/CMakeLists.txt b/future/CMakeLists.txt deleted file mode 100644 index 6c939a9..0000000 --- a/future/CMakeLists.txt +++ /dev/null @@ -1,111 +0,0 @@ -function(add_bench BENCH_PATH) - string(REPLACE "/" "_" BENCH_NAME ${BENCH_PATH}) - add_executable(${BENCH_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/${BENCH_PATH}.cpp) - target_link_libraries(${BENCH_NAME} - PRIVATE ${GTEST_BOTH_LIBRARIES} - PRIVATE benchmark::benchmark - PRIVATE ${PROJECT_NAME} - ) - - if (STD) - target_compile_definitions(${BENCH_NAME} PUBLIC STD_ENABLE) - endif () - if (YACLIB) - target_compile_definitions(${BENCH_NAME} PUBLIC YACLIB_ENABLE) - endif () - if (FOLLY) - target_compile_definitions(${BENCH_NAME} PUBLIC FOLLY_ENABLE) - endif () - if (BOOST_THREAD) - target_compile_definitions(${BENCH_NAME} PUBLIC BOOST_THREAD_ENABLE) - endif () - if (QT) - target_compile_definitions(${BENCH_NAME} PUBLIC QT_ENABLE) - endif () - if (EXPERIMENTAL) - target_compile_definitions(${BENCH_NAME} PUBLIC EXPERIMENTAL_ENABLE) - endif () - - add_test(NAME ${BENCH_NAME} COMMAND ${BENCH_NAME}) -endfunction() - -find_package(Threads REQUIRED) -link_libraries(Threads::Threads) - -if (YACLIB) - FetchContent_Declare(yaclib - GIT_REPOSITORY https://github.com/YACLib/YACLib.git - GIT_TAG "${YACLIB}" - ) - set(YACLIB_CXX_STANDARD 20) - list(APPEND YACLIB_FLAGS "ATOMIC_EVENT") - FetchContent_MakeAvailable(yaclib) - link_libraries(yaclib) -endif () -if (FOLLY) - find_package(glog CONFIG REQUIRED) # Ad-hoc - find_package(folly CONFIG REQUIRED) - include_directories(${FOLLY_INCLUDE_DIR}) - link_libraries(Folly::folly) -endif () -if (BOOST_THREAD) - find_package(Boost COMPONENTS thread REQUIRED) - link_libraries(Boost::thread) -endif () -if (QT) - find_package(Qt6 COMPONENTS Concurrent REQUIRED) - link_libraries(Qt6::Concurrent) -endif () -if (EXPERIMENTAL) - add_link_options(-lc++experimental) -endif () -# Bench ################################################################################################################ -macro(add_files) - set(BENCH_HEADERS ${BENCH_HEADERS} PARENT_SCOPE) - set(BENCH_SOURCES ${BENCH_SOURCES} PARENT_SCOPE) -endmacro() -set(BENCH_HEADERS - ${BENCH_SOURCE_DIR}/future/bind/all.hpp - ) -set(BENCH_SOURCES - ) - -if (STD) - add_subdirectory(bind/std) -endif () -if (YACLIB) - add_subdirectory(bind/yaclib) -endif () -if (FOLLY) - add_subdirectory(bind/folly) -endif () -if (BOOST_THREAD) - add_subdirectory(bind/boost_thread) -endif () -if (QT) - add_subdirectory(bind/qt) -endif () -if (EXPERIMENTAL) - add_subdirectory(bind/experimental) -endif () - -add_subdirectory(bench) - -add_library(${PROJECT_NAME} STATIC) - -target_sources(${PROJECT_NAME} - PRIVATE ${BENCH_SOURCES} - PRIVATE ${BENCH_HEADERS} - ) - -target_include_directories(${PROJECT_NAME} - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} - PUBLIC ${BENCH_SOURCE_DIR}/util - ) - -target_link_libraries(${PROJECT_NAME} - PRIVATE ${GTEST_BOTH_LIBRARIES} - PUBLIC benchmark::benchmark - ) - -add_bench(future) # TODO separate diff --git a/future/bench/CMakeLists.txt b/future/bench/CMakeLists.txt deleted file mode 100644 index e190326..0000000 --- a/future/bench/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -list(APPEND BENCH_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/complex.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/creation.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/contention.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/thens.hpp - ) - -list(APPEND BENCH_SOURCES - ) - -add_files() diff --git a/future/bench/thens.hpp b/future/bench/thens.hpp deleted file mode 100644 index 42ac700..0000000 --- a/future/bench/thens.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#include - -#include - -namespace bench { - -template -void Then(benchmark::State& state) { - auto const count = state.range(0); - auto const type = state.range(1); - std::unique_ptr executor; - if (type != 0) { - executor = std::make_unique(1); - } - for (auto _ : state) { - if (type == 0) { - Library::SomeThens(nullptr, count, false); - } else { - Library::SomeThens(executor.get(), count, type == 2); - executor->Restart(); - } - } -} - -} // namespace bench diff --git a/future/bind/all.hpp b/future/bind/all.hpp deleted file mode 100644 index 0dfeda8..0000000 --- a/future/bind/all.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#ifdef STD_ENABLE -#include -#endif - -#ifdef YACLIB_ENABLE -#include -#endif - -#ifdef FOLLY_ENABLE -#include -#endif - -#ifdef BOOST_THREAD_ENABLE -#include -#endif - -#ifdef QT_ENABLE -#include -#endif - -#ifdef EXPERIMENTAL_ENABLE -#include -#endif diff --git a/future/bind/boost_thread/CMakeLists.txt b/future/bind/boost_thread/CMakeLists.txt deleted file mode 100644 index b9b2e60..0000000 --- a/future/bind/boost_thread/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -list(APPEND BENCH_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/future.hpp - ) -list(APPEND BENCH_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/future.cpp - ) - -add_files() diff --git a/future/bind/experimental/CMakeLists.txt b/future/bind/experimental/CMakeLists.txt deleted file mode 100644 index b9b2e60..0000000 --- a/future/bind/experimental/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -list(APPEND BENCH_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/future.hpp - ) -list(APPEND BENCH_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/future.cpp - ) - -add_files() diff --git a/future/bind/folly/CMakeLists.txt b/future/bind/folly/CMakeLists.txt deleted file mode 100644 index b9b2e60..0000000 --- a/future/bind/folly/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -list(APPEND BENCH_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/future.hpp - ) -list(APPEND BENCH_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/future.cpp - ) - -add_files() diff --git a/future/bind/folly/future.cpp b/future/bind/folly/future.cpp deleted file mode 100644 index d00315e..0000000 --- a/future/bind/folly/future.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include -#include - -#include -#include - -#include -#include -#include -#include - -namespace bench { -namespace { - -template -T Incr(folly::Try&& t) { - return t.value() + 1; -} - -folly::Future Thens(folly::Future f, std::size_t n, bool is_executor) { - for (std::size_t i = 0; i != n; ++i) { - if (is_executor) { - f = std::move(f).then(Incr); - } else { - f = std::move(f).thenInline(Incr); - } - } - return f; -} - -} // namespace -namespace detail::fy { - -TestExecutor::TestExecutor(std::size_t num_threads) { - num_threads = std::max(std::size_t{1}, num_threads); - _workers.reserve(num_threads); - for (std::size_t i = 0; i != num_threads; ++i) { - _workers.emplace_back([this] { - std::unique_lock lock{_m}; - while (true) { - while (!_tasks.empty()) { - auto work = std::move(_tasks.front()); - _tasks.pop(); - lock.unlock(); - work(); - lock.lock(); - } - if (_stop) { - return; - } - _cv.wait(lock); - } - }); - } -} - -void TestExecutor::Restart() { - std::lock_guard lock{_m}; - _tasks = {}; -} - -void TestExecutor::Join() { - { - std::lock_guard lock{_m}; - _stop = true; - } - _cv.notify_all(); - for (auto& worker : _workers) { - if (worker.joinable()) { - worker.join(); - } - } -} - -TestExecutor::~TestExecutor() { - Join(); -} - -void TestExecutor::add(folly::Func f) { - { - std::lock_guard lock{_m}; - _tasks.push(std::move(f)); - } - _cv.notify_one(); -} - -} // namespace detail::fy - -void Folly::CreateFuture() { - std::ignore = folly::makeFuture(42); -} - -void Folly::PromiseAndFuture() { - folly::Promise p; - folly::Future f = p.getFuture(); - std::move(p).setValue(42); - std::ignore = std::move(f).get(); -} - -void Folly::SomeThens(Executor* executor, size_t n, bool no_inline) { - const bool is_executor = executor != nullptr; - auto f = folly::makeFuture(42).via(executor); - f = Thens(std::move(f), n, is_executor && no_inline); - f = Thens(std::move(f), 1, is_executor); - f = Thens(std::move(f), n, is_executor && no_inline); - f.wait(); -} - -void Folly::NoContention(benchmark::State& state) { - state.PauseTiming(); - - std::vector> promises(kContentionIteration); - std::vector> futures; - futures.reserve(kContentionIteration); - - std::promise p_producer; - auto f_producer = p_producer.get_future(); - - for (auto& p : promises) { - futures.push_back(p.getFuture().thenInline(Incr)); - } - - std::thread producer{[&] { - p_producer.set_value(); - for (auto& p : promises) { - std::move(p).setValue(42); - } - }}; - - f_producer.wait(); - - state.ResumeTiming(); - - producer.join(); -} - -void Folly::Contention(benchmark::State& state) { - state.PauseTiming(); - - std::vector> promises(kContentionIteration); - std::vector> futures; - futures.reserve(kContentionIteration); - - for (auto& p : promises) { - futures.push_back(p.getFuture()); - } - - BusySemaphoreSPSC semaphore; - std::promise p_consumer; - auto f_consumer = p_consumer.get_future(); - std::promise p_producer; - auto f_producer = p_producer.get_future(); - - auto producer = std::thread([&] { - p_producer.set_value(); - for (auto& p : promises) { - semaphore.Release(); - std::move(p).setValue(42); - } - }); - auto consumer = std::thread([&] { - p_consumer.set_value(); - for (auto& f : futures) { - semaphore.Acquire(); - f = std::move(f).thenInline(Incr); - } - }); - - f_consumer.wait(); - f_producer.wait(); - - state.ResumeTiming(); - - producer.join(); - consumer.join(); -} - -} // namespace bench diff --git a/future/bind/qt/CMakeLists.txt b/future/bind/qt/CMakeLists.txt deleted file mode 100644 index b9b2e60..0000000 --- a/future/bind/qt/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -list(APPEND BENCH_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/future.hpp - ) -list(APPEND BENCH_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/future.cpp - ) - -add_files() diff --git a/future/bind/yaclib/future.cpp b/future/bind/yaclib/future.cpp deleted file mode 100644 index b53c6e5..0000000 --- a/future/bind/yaclib/future.cpp +++ /dev/null @@ -1,189 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include - -#include - -namespace bench { -namespace { - -template -T Incr(yaclib::Result&& r) { - return std::move(r).Ok() + 1; -} - -yaclib::Future Thens(yaclib::Future f, std::size_t n, bool is_executor) { - for (std::size_t i = 0; i != n; ++i) { - if (is_executor) { - f = std::move(f).Then(Incr); - } else { - f = std::move(f).ThenInline(Incr); - } - } - return f; -} - -} // namespace -namespace detail::yb { - -TestExecutor::TestExecutor(std::size_t num_threads) { - num_threads = std::max(std::size_t{1}, num_threads); - _workers.reserve(num_threads); - for (std::size_t i = 0; i != num_threads; ++i) { - _workers.emplace_back([this] { - std::unique_lock lock{_m}; - while (true) { - while (!_tasks.Empty()) { - auto& task = _tasks.PopFront(); - lock.unlock(); - task.Call(); - task.DecRef(); - lock.lock(); - } - if (_stop) { - return; - } - _cv.wait(lock); - } - }); - } -} - -void TestExecutor::Restart() { - std::lock_guard lock{_m}; -} - -void TestExecutor::Join() { - { - std::lock_guard lock{_m}; - _stop = true; - } - _cv.notify_all(); - for (auto& worker : _workers) { - if (worker.joinable()) { - worker.join(); - } - } -} - -TestExecutor::~TestExecutor() { - Join(); -} - -yaclib::IExecutor::Type TestExecutor::Tag() const { - return Type::Custom; -} - -bool TestExecutor::Submit(yaclib::ITask& task) noexcept { - task.IncRef(); - { - std::lock_guard guard{_m}; - _tasks.PushBack(task); - } - _cv.notify_one(); - return true; -} - -} // namespace detail::yb - -void YACLib::CreateFuture() { - std::ignore = yaclib::MakeFuture(42); -} - -void YACLib::PromiseAndFuture() { - auto [f, p] = yaclib::MakeContract(); - std::move(p).Set(42); - std::ignore = std::move(f).Get().Ok(); -} - -void YACLib::SomeThens(YACLib::Executor* executor, size_t n, bool no_inline) { - const bool is_executor = executor != nullptr; - auto f = yaclib::MakeFuture(42).Via(executor); - f = Thens(std::move(f), n, is_executor && no_inline); - f = Thens(std::move(f), 1, is_executor); - f = Thens(std::move(f), n, is_executor && no_inline); - Wait(f); -} - -void YACLib::NoContention(benchmark::State& state) { - state.PauseTiming(); - - std::vector> promises; - std::vector> futures; - promises.reserve(kContentionIteration); - futures.reserve(kContentionIteration); - - std::promise p_producer; - auto f_producer = p_producer.get_future(); - - for (std::size_t i = 0; i != kContentionIteration; ++i) { - auto [f, p] = yaclib::MakeContract(); - promises.emplace_back(std::move(p)); - futures.emplace_back(std::move(f).ThenInline(Incr)); - } - - auto producer = std::thread([&]() mutable { - p_producer.set_value(); - - for (auto& p : promises) { - std::move(p).Set(42); - } - }); - - f_producer.wait(); - - state.ResumeTiming(); - - producer.join(); -} - -void YACLib::Contention(benchmark::State& state) { - state.PauseTiming(); - - std::vector> promises; - std::vector> futures; - promises.reserve(kContentionIteration); - futures.reserve(kContentionIteration); - - for (std::size_t i = 0; i != kContentionIteration; ++i) { - auto [f, p] = yaclib::MakeContract(); - futures.push_back(std::move(f)); - promises.push_back(std::move(p)); - } - - BusySemaphoreSPSC semaphore; - std::promise p_consumer; - auto f_consumer = p_consumer.get_future(); - std::promise p_producer; - auto f_producer = p_producer.get_future(); - - auto producer = std::thread([&] { - p_producer.set_value(); - for (auto& p : promises) { - semaphore.Release(); - std::move(p).Set(42); - } - }); - auto consumer = std::thread([&] { - p_consumer.set_value(); - for (auto& f : futures) { - semaphore.Acquire(); - f = std::move(f).ThenInline(Incr); - } - }); - - f_consumer.wait(); - f_producer.wait(); - - state.ResumeTiming(); - - producer.join(); - consumer.join(); -} - -} // namespace bench diff --git a/script/build.sh b/script/build.sh index 4c21e31..88cbb91 100755 --- a/script/build.sh +++ b/script/build.sh @@ -4,19 +4,19 @@ cmake -S . -B ./build_clang_libcxx \ -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ - -DLIBCXX=ON -DSTD=ON -DYACLIB=main + -DLIBCXX=ON -DSTD=ON -DYACLIB=v2022.08.23 cmake -S . -B ./build_clang_libstdcxx \ -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ - -DSTD=ON -DYACLIB=main -DFOLLY=ON -DBOOST_THREAD=ON + -DSTD=ON -DYACLIB=v2022.08.23 -DFOLLY=ON -DBOOST_THREAD=ON cmake -S . -B ./build_gcc_libstdcxx \ -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ \ - -DSTD=ON -DYACLIB=main -DFOLLY=ON -DBOOST_THREAD=ON + -DSTD=ON -DYACLIB=v2022.08.23 -DFOLLY=ON -DBOOST_THREAD=ON ninja -C ./build_clang_libcxx diff --git a/script/run.sh b/script/run.sh index 6e0f75c..9d14a05 100755 --- a/script/run.sh +++ b/script/run.sh @@ -2,6 +2,10 @@ for i in "$@"; do case $i in + --name=*) + name="${i#*=}" + shift + ;; --proc=*) proc="${i#*=}" shift @@ -14,23 +18,28 @@ for i in "$@"; do esac done +if [[ -z "$name" ]]; then + echo "You should specify --name=... option" + exit 1 +fi + if [[ -z "$proc" ]]; then echo "You should specify --proc=... option" exit 1 fi -dir="./future/result/$proc" +dir="./$name/result/$proc" mkdir -p "$dir/clang_libcxx" mkdir -p "$dir/clang_libstdcxx" mkdir -p "$dir/gcc_libstdcxx" -./build_clang_libcxx/future/future --benchmark_out_format="json" --benchmark_out="$dir/clang_libcxx/data.json" +./build_clang_libcxx/$name/$name --benchmark_out_format="json" --benchmark_out="$dir/clang_libcxx/data.json" -./build_clang_libstdcxx/future/future --benchmark_out_format="json" --benchmark_out="$dir/clang_libstdcxx/data.json" +./build_clang_libstdcxx/$name/$name --benchmark_out_format="json" --benchmark_out="$dir/clang_libstdcxx/data.json" -./build_gcc_libstdcxx/future/future --benchmark_out_format="json" --benchmark_out="$dir/gcc_libstdcxx/data.json" +./build_gcc_libstdcxx/$name/$name --benchmark_out_format="json" --benchmark_out="$dir/gcc_libstdcxx/data.json" -echo "- [$proc]($proc/)" >> future/result/RESULTS.md +echo "- [$proc]($proc/)" >> $name/result/RESULTS.md git add . diff --git a/test/coro/coro.cpp b/test/coro/coro.cpp new file mode 100644 index 0000000..0d651a0 --- /dev/null +++ b/test/coro/coro.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +using namespace bench; + +BENCH_YACLIB(Reschedule)->UseRealTime(); +BENCH_CPPCORO(Reschedule)->UseRealTime(); +BENCH_FOLLY(Reschedule)->UseRealTime(); + +BENCH_YACLIB(Latch)->UseRealTime()->Args({600}); +BENCH_CPPCORO(Latch)->UseRealTime()->Args({600}); +// BENCH_FOLLY(Latch)->UseRealTime()->Args({600}); + +// BENCH_STD(AsyncMutex)->UseRealTime(); +BENCH_YACLIB(AsyncMutex)->UseRealTime(); +BENCH_CPPCORO(AsyncMutex)->UseRealTime(); +// BENCH_FOLLY(AsyncMutex)->UseRealTime(); + +// BENCH_STD(Collatz)->UseRealTime()->ArgsProduct({{1, 2, 3, 7, 9, 25, 27, 313, 871, 2463, 6171}, {0}}); +BENCH_YACLIB(CollatzEager)->UseRealTime()->ArgsProduct({{1, 2, 3, 7, 9, 25, 27, 313, 871, 2463, 6171}, {0}}); +BENCH_CPPCORO(CollatzLazy)->UseRealTime()->ArgsProduct({{1, 2, 3, 7, 9, 25, 27, 313, 871, 2463, 6171}, {0}}); +BENCH_FOLLY(CollatzLazy)->UseRealTime()->ArgsProduct({{1, 2, 3, 7, 9, 25, 27, 313, 871, 2463, 6171}, {0}}); + +// BENCH_STD(Fibonacci)->UseRealTime()->ArgsProduct({{1, 2, 4, 8, 16}, {0}}); +BENCH_YACLIB(Fibonacci)->UseRealTime()->ArgsProduct({{1, 2, 4, 8, 16}, {0}}); +BENCH_CPPCORO(Fibonacci)->UseRealTime()->ArgsProduct({{1, 2, 4, 8, 16}, {0}}); +BENCH_FOLLY(Fibonacci)->UseRealTime()->ArgsProduct({{1, 2, 4, 8, 16}, {0}}); + +} // namespace + +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + if (benchmark::ReportUnrecognizedArguments(argc, argv)) { + return 1; + } + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/future/future.cpp b/test/future/future.cpp similarity index 75% rename from future/future.cpp rename to test/future/future.cpp index ec2980c..b8b8efe 100644 --- a/future/future.cpp +++ b/test/future/future.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include #include +#include #include @@ -14,6 +14,7 @@ using namespace bench; BENCH_STD(ConstantFuture)->UseRealTime(); BENCH_YACLIB(ConstantFuture)->UseRealTime(); BENCH_FOLLY(ConstantFuture)->UseRealTime(); +BENCH_ARANGODB(ConstantFuture)->UseRealTime(); BENCH_QT(ConstantFuture)->UseRealTime(); BENCH_BOOST_THREAD(ConstantFuture)->UseRealTime(); BENCH_EXPERIMENTAL(ConstantFuture)->UseRealTime(); @@ -21,111 +22,129 @@ BENCH_EXPERIMENTAL(ConstantFuture)->UseRealTime(); BENCH_STD(PromiseAndFuture)->UseRealTime(); BENCH_YACLIB(PromiseAndFuture)->UseRealTime(); BENCH_FOLLY(PromiseAndFuture)->UseRealTime(); +BENCH_ARANGODB(PromiseAndFuture)->UseRealTime(); BENCH_BOOST_THREAD(PromiseAndFuture)->UseRealTime(); BENCH_QT(PromiseAndFuture)->UseRealTime(); BENCH_EXPERIMENTAL(PromiseAndFuture)->UseRealTime(); BENCH_YACLIB(Then)->ArgsProduct({{0, 1, 2, 4, 8, 16, 32, 64}, {0, 1, 2}})->UseRealTime(); BENCH_FOLLY(Then)->ArgsProduct({{0, 1, 2, 4, 8, 16, 32, 64}, {0, 1, 2}})->UseRealTime(); +BENCH_ARANGODB(Then)->ArgsProduct({{0, 1, 2, 4, 8, 16, 32, 64}, {0, 1, 2}})->UseRealTime(); BENCH_BOOST_THREAD(Then)->ArgsProduct({{0, 1, 2, 4, 8, 16, 32, 64}, {0, 1, 2}})->UseRealTime(); BENCH_QT(Then)->ArgsProduct({{0, 1, 2, 4, 8, 16, 32, 64}, {0, 1, 2}})->UseRealTime(); BENCH_EXPERIMENTAL(Then)->ArgsProduct({{0, 1, 2, 4, 8, 16, 32, 64}, {0, 1, 2}})->UseRealTime(); BENCH_YACLIB(ComplexBlob, 0)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 0)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 0)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 0)->UseRealTime(); BENCH_QT(ComplexBlob, 0)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 0)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 2)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 2)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 2)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 2)->UseRealTime(); BENCH_QT(ComplexBlob, 2)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 2)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 4)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 4)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 4)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 4)->UseRealTime(); BENCH_QT(ComplexBlob, 4)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 4)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 8)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 8)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 8)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 8)->UseRealTime(); BENCH_QT(ComplexBlob, 8)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 8)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 16)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 16)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 16)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 16)->UseRealTime(); BENCH_QT(ComplexBlob, 16)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 16)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 32)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 32)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 32)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 32)->UseRealTime(); BENCH_QT(ComplexBlob, 32)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 32)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 64)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 64)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 64)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 64)->UseRealTime(); BENCH_QT(ComplexBlob, 64)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 64)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 128)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 128)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 128)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 128)->UseRealTime(); BENCH_QT(ComplexBlob, 128)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 128)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 256)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 256)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 256)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 256)->UseRealTime(); BENCH_QT(ComplexBlob, 256)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 256)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 512)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 512)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 512)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 512)->UseRealTime(); BENCH_QT(ComplexBlob, 512)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 512)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 1024)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 1024)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 1024)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 1024)->UseRealTime(); BENCH_QT(ComplexBlob, 1024)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 1024)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 2048)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 2048)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 2048)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 2048)->UseRealTime(); BENCH_QT(ComplexBlob, 2048)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 2048)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 4096)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 4096)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 4096)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 4096)->UseRealTime(); BENCH_QT(ComplexBlob, 4096)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 4096)->UseRealTime(); BENCH_YACLIB(ComplexBlob, 8192)->UseRealTime(); BENCH_FOLLY(ComplexBlob, 8192)->UseRealTime(); +BENCH_ARANGODB(ComplexBlob, 8192)->UseRealTime(); BENCH_BOOST_THREAD(ComplexBlob, 8192)->UseRealTime(); BENCH_QT(ComplexBlob, 8192)->UseRealTime(); BENCH_EXPERIMENTAL(ComplexBlob, 8192)->UseRealTime(); -BENCH_YACLIB(NoContention)->UseRealTime(); -BENCH_FOLLY(NoContention)->UseRealTime(); -BENCH_BOOST_THREAD(NoContention)->UseRealTime(); -BENCH_QT(NoContention)->UseRealTime(); -BENCH_EXPERIMENTAL(NoContention)->UseRealTime(); - -BENCH_YACLIB(Contention)->UseRealTime(); -BENCH_FOLLY(Contention)->UseRealTime(); -BENCH_BOOST_THREAD(Contention)->UseRealTime(); -BENCH_QT(Contention)->UseRealTime(); -BENCH_EXPERIMENTAL(Contention)->UseRealTime(); +BENCH_YACLIB(NoContention)->UseRealTime()->Args({10000}); +BENCH_FOLLY(NoContention)->UseRealTime()->Args({10000}); +BENCH_ARANGODB(NoContention)->UseRealTime()->Args({10000}); +BENCH_BOOST_THREAD(NoContention)->UseRealTime()->Args({10000}); +BENCH_QT(NoContention)->UseRealTime()->Args({10000}); +BENCH_EXPERIMENTAL(NoContention)->UseRealTime()->Args({10000}); + +BENCH_YACLIB(Contention)->UseRealTime()->Args({10000}); +BENCH_FOLLY(Contention)->UseRealTime()->Args({10000}); +BENCH_ARANGODB(Contention)->UseRealTime()->Args({10000}); +BENCH_BOOST_THREAD(Contention)->UseRealTime()->Args({10000}); +BENCH_QT(Contention)->UseRealTime()->Args({10000}); +BENCH_EXPERIMENTAL(Contention)->UseRealTime()->Args({10000}); } // namespace diff --git a/future/result/RESULTS.md b/test/future/result/RESULTS.md similarity index 100% rename from future/result/RESULTS.md rename to test/future/result/RESULTS.md diff --git a/future/result/Ryzen_7_3700X/README.md b/test/future/result/Ryzen_7_3700X/README.md similarity index 100% rename from future/result/Ryzen_7_3700X/README.md rename to test/future/result/Ryzen_7_3700X/README.md diff --git a/future/result/Ryzen_7_3700X/clang_libcxx/data.json b/test/future/result/Ryzen_7_3700X/clang_libcxx/data.json similarity index 100% rename from future/result/Ryzen_7_3700X/clang_libcxx/data.json rename to test/future/result/Ryzen_7_3700X/clang_libcxx/data.json diff --git a/future/result/Ryzen_7_3700X/clang_libstdcxx/data.json b/test/future/result/Ryzen_7_3700X/clang_libstdcxx/data.json similarity index 100% rename from future/result/Ryzen_7_3700X/clang_libstdcxx/data.json rename to test/future/result/Ryzen_7_3700X/clang_libstdcxx/data.json diff --git a/future/result/Ryzen_7_3700X/gcc_libstdcxx/data.json b/test/future/result/Ryzen_7_3700X/gcc_libstdcxx/data.json similarity index 100% rename from future/result/Ryzen_7_3700X/gcc_libstdcxx/data.json rename to test/future/result/Ryzen_7_3700X/gcc_libstdcxx/data.json diff --git a/future/result/Ryzen_9_5900X/README.md b/test/future/result/Ryzen_9_5900X/README.md similarity index 100% rename from future/result/Ryzen_9_5900X/README.md rename to test/future/result/Ryzen_9_5900X/README.md diff --git a/future/result/Ryzen_9_5900X/clang_libcxx/data.json b/test/future/result/Ryzen_9_5900X/clang_libcxx/data.json similarity index 100% rename from future/result/Ryzen_9_5900X/clang_libcxx/data.json rename to test/future/result/Ryzen_9_5900X/clang_libcxx/data.json diff --git a/future/result/Ryzen_9_5900X/clang_libstdcxx/data.json b/test/future/result/Ryzen_9_5900X/clang_libstdcxx/data.json similarity index 100% rename from future/result/Ryzen_9_5900X/clang_libstdcxx/data.json rename to test/future/result/Ryzen_9_5900X/clang_libstdcxx/data.json diff --git a/future/result/Ryzen_9_5900X/gcc_libstdcxx/data.json b/test/future/result/Ryzen_9_5900X/gcc_libstdcxx/data.json similarity index 100% rename from future/result/Ryzen_9_5900X/gcc_libstdcxx/data.json rename to test/future/result/Ryzen_9_5900X/gcc_libstdcxx/data.json diff --git a/future/result/i5-3210M/clang_libcxx/data.json b/test/future/result/i5-3210M/clang_libcxx/data.json similarity index 100% rename from future/result/i5-3210M/clang_libcxx/data.json rename to test/future/result/i5-3210M/clang_libcxx/data.json diff --git a/future/result/i5-3210M/clang_libstdcxx/data.json b/test/future/result/i5-3210M/clang_libstdcxx/data.json similarity index 100% rename from future/result/i5-3210M/clang_libstdcxx/data.json rename to test/future/result/i5-3210M/clang_libstdcxx/data.json diff --git a/future/result/i5-3210M/gcc_libstdcxx/data.json b/test/future/result/i5-3210M/gcc_libstdcxx/data.json similarity index 100% rename from future/result/i5-3210M/gcc_libstdcxx/data.json rename to test/future/result/i5-3210M/gcc_libstdcxx/data.json diff --git a/future/result/i5-5300U/clang_libcxx/data.json b/test/future/result/i5-5300U/clang_libcxx/data.json similarity index 100% rename from future/result/i5-5300U/clang_libcxx/data.json rename to test/future/result/i5-5300U/clang_libcxx/data.json diff --git a/future/result/i5-5300U/clang_libstdcxx/data.json b/test/future/result/i5-5300U/clang_libstdcxx/data.json similarity index 100% rename from future/result/i5-5300U/clang_libstdcxx/data.json rename to test/future/result/i5-5300U/clang_libstdcxx/data.json diff --git a/future/result/i5-5300U/gcc_libstdcxx/data.json b/test/future/result/i5-5300U/gcc_libstdcxx/data.json similarity index 100% rename from future/result/i5-5300U/gcc_libstdcxx/data.json rename to test/future/result/i5-5300U/gcc_libstdcxx/data.json diff --git a/future/result/i7-11850H/README.md b/test/future/result/i7-11850H/README.md similarity index 100% rename from future/result/i7-11850H/README.md rename to test/future/result/i7-11850H/README.md diff --git a/future/result/i7-11850H/clang_libcxx/data.json b/test/future/result/i7-11850H/clang_libcxx/data.json similarity index 100% rename from future/result/i7-11850H/clang_libcxx/data.json rename to test/future/result/i7-11850H/clang_libcxx/data.json diff --git a/future/result/i7-11850H/clang_libstdcxx/data.json b/test/future/result/i7-11850H/clang_libstdcxx/data.json similarity index 100% rename from future/result/i7-11850H/clang_libstdcxx/data.json rename to test/future/result/i7-11850H/clang_libstdcxx/data.json diff --git a/future/result/i7-11850H/gcc_libstdcxx/data.json b/test/future/result/i7-11850H/gcc_libstdcxx/data.json similarity index 100% rename from future/result/i7-11850H/gcc_libstdcxx/data.json rename to test/future/result/i7-11850H/gcc_libstdcxx/data.json diff --git a/future/result/i7-12700KF/README.md b/test/future/result/i7-12700KF/README.md similarity index 98% rename from future/result/i7-12700KF/README.md rename to test/future/result/i7-12700KF/README.md index b1aef06..f69413e 100644 --- a/future/result/i7-12700KF/README.md +++ b/test/future/result/i7-12700KF/README.md @@ -1,336 +1,336 @@ -```2022-03-30T16:54:44+03:00 -Running ./build_clang_libcxx/future/future -Run on (20 X 3609.6 MHz CPU s) -CPU Caches: - L1 Data 48 KiB (x10) - L1 Instruction 32 KiB (x10) - L2 Unified 1280 KiB (x10) - L3 Unified 25600 KiB (x1) -Load Average: 0.73, 1.49, 1.20 ------------------------------------------------------------------------------ -Benchmark Time CPU Iterations ------------------------------------------------------------------------------ -ConstantFuture(Std)/real_time 31.8 ns 31.8 ns 22190553 -ConstantFuture(YACLib)/real_time 12.9 ns 12.9 ns 54425219 -PromiseAndFuture(Std)/real_time 42.0 ns 42.0 ns 16666191 -PromiseAndFuture(YACLib)/real_time 23.6 ns 23.6 ns 29757066 -Then(YACLib)/0/0/real_time 51.6 ns 51.6 ns 13814340 -Then(YACLib)/1/0/real_time 108 ns 108 ns 6449491 -Then(YACLib)/2/0/real_time 163 ns 163 ns 4338925 -Then(YACLib)/4/0/real_time 276 ns 276 ns 2537790 -Then(YACLib)/8/0/real_time 498 ns 498 ns 1404833 -Then(YACLib)/16/0/real_time 941 ns 941 ns 740001 -Then(YACLib)/32/0/real_time 1813 ns 1813 ns 385745 -Then(YACLib)/64/0/real_time 3581 ns 3581 ns 195741 -Then(YACLib)/0/1/real_time 22629 ns 17947 ns 31719 -Then(YACLib)/1/1/real_time 22633 ns 18097 ns 31213 -Then(YACLib)/2/1/real_time 22817 ns 18246 ns 24594 -Then(YACLib)/4/1/real_time 23345 ns 18675 ns 28679 -Then(YACLib)/8/1/real_time 26533 ns 20614 ns 27884 -Then(YACLib)/16/1/real_time 24612 ns 19967 ns 27022 -Then(YACLib)/32/1/real_time 27010 ns 21976 ns 27467 -Then(YACLib)/64/1/real_time 26491 ns 23182 ns 25875 -Then(YACLib)/0/2/real_time 24122 ns 18788 ns 30608 -Then(YACLib)/1/2/real_time 23173 ns 18220 ns 29999 -Then(YACLib)/2/2/real_time 25244 ns 19339 ns 29579 -Then(YACLib)/4/2/real_time 24043 ns 18557 ns 29058 -Then(YACLib)/8/2/real_time 26520 ns 19993 ns 29613 -Then(YACLib)/16/2/real_time 25730 ns 19769 ns 27449 -Then(YACLib)/32/2/real_time 30315 ns 22259 ns 25301 -Then(YACLib)/64/2/real_time 31999 ns 23949 ns 21286 -ComplexBlob(YACLib)/0/real_time 10374 ns 10374 ns 67308 -ComplexBlob(YACLib)/2/real_time 9801 ns 9801 ns 71469 -ComplexBlob(YACLib)/4/real_time 9827 ns 9827 ns 71541 -ComplexBlob(YACLib)/8/real_time 9805 ns 9805 ns 71593 -ComplexBlob(YACLib)/16/real_time 11154 ns 11154 ns 62962 -ComplexBlob(YACLib)/32/real_time 11125 ns 11125 ns 62780 -ComplexBlob(YACLib)/64/real_time 10069 ns 10069 ns 69875 -ComplexBlob(YACLib)/128/real_time 10646 ns 10646 ns 65945 -ComplexBlob(YACLib)/256/real_time 11458 ns 11458 ns 60897 -ComplexBlob(YACLib)/512/real_time 11793 ns 11793 ns 58843 -ComplexBlob(YACLib)/1024/real_time 19296 ns 19296 ns 36463 -ComplexBlob(YACLib)/2048/real_time 22367 ns 22367 ns 31275 -ComplexBlob(YACLib)/4096/real_time 32531 ns 32531 ns 21726 -ComplexBlob(YACLib)/8192/real_time 127390 ns 127390 ns 5476 -NoContention(YACLib)/real_time 601443 ns 302777 ns 1076 -Contention(YACLib)/real_time 757511 ns 214086 ns 937 -2022-03-30T16:55:25+03:00 -Running ./build_clang_libstdcxx/future/future -Run on (20 X 3609.6 MHz CPU s) -CPU Caches: - L1 Data 48 KiB (x10) - L1 Instruction 32 KiB (x10) - L2 Unified 1280 KiB (x10) - L3 Unified 25600 KiB (x1) -Load Average: 0.79, 1.40, 1.18 ----------------------------------------------------------------------------------- -Benchmark Time CPU Iterations ----------------------------------------------------------------------------------- -ConstantFuture(Std)/real_time 91.3 ns 91.3 ns 7526855 -ConstantFuture(YACLib)/real_time 12.7 ns 12.7 ns 54310592 -ConstantFuture(Folly)/real_time 12.4 ns 12.4 ns 57598706 -ConstantFuture(BoostThread)/real_time 88.2 ns 88.2 ns 7986037 -PromiseAndFuture(Std)/real_time 96.3 ns 96.3 ns 7013497 -PromiseAndFuture(YACLib)/real_time 22.4 ns 22.4 ns 30838971 -PromiseAndFuture(Folly)/real_time 49.0 ns 49.0 ns 14279965 -PromiseAndFuture(BoostThread)/real_time 92.3 ns 92.3 ns 7542843 -Then(YACLib)/0/0/real_time 54.9 ns 54.9 ns 12825362 -Then(YACLib)/1/0/real_time 111 ns 111 ns 6207085 -Then(YACLib)/2/0/real_time 168 ns 168 ns 4195324 -Then(YACLib)/4/0/real_time 284 ns 284 ns 2479942 -Then(YACLib)/8/0/real_time 510 ns 510 ns 1360601 -Then(YACLib)/16/0/real_time 961 ns 961 ns 710419 -Then(YACLib)/32/0/real_time 1868 ns 1868 ns 376092 -Then(YACLib)/64/0/real_time 3646 ns 3646 ns 192112 -Then(YACLib)/0/1/real_time 23072 ns 18173 ns 30602 -Then(YACLib)/1/1/real_time 22938 ns 18229 ns 28529 -Then(YACLib)/2/1/real_time 22915 ns 18264 ns 24189 -Then(YACLib)/4/1/real_time 23306 ns 18642 ns 30023 -Then(YACLib)/8/1/real_time 25767 ns 20203 ns 29130 -Then(YACLib)/16/1/real_time 24621 ns 20051 ns 28781 -Then(YACLib)/32/1/real_time 27187 ns 22144 ns 27410 -Then(YACLib)/64/1/real_time 27358 ns 23541 ns 26298 -Then(YACLib)/0/2/real_time 24657 ns 19039 ns 31663 -Then(YACLib)/1/2/real_time 23234 ns 18163 ns 28119 -Then(YACLib)/2/2/real_time 25039 ns 19298 ns 28815 -Then(YACLib)/4/2/real_time 24177 ns 18656 ns 29070 -Then(YACLib)/8/2/real_time 26075 ns 19834 ns 28303 -Then(YACLib)/16/2/real_time 26422 ns 19965 ns 27358 -Then(YACLib)/32/2/real_time 27420 ns 20722 ns 25907 -Then(YACLib)/64/2/real_time 32485 ns 24018 ns 20179 -Then(Folly)/0/0/real_time 97.0 ns 97.0 ns 7167378 -Then(Folly)/1/0/real_time 217 ns 217 ns 3212924 -Then(Folly)/2/0/real_time 350 ns 350 ns 2041123 -Then(Folly)/4/0/real_time 605 ns 605 ns 1149099 -Then(Folly)/8/0/real_time 1112 ns 1112 ns 628177 -Then(Folly)/16/0/real_time 2131 ns 2131 ns 330572 -Then(Folly)/32/0/real_time 4143 ns 4143 ns 169298 -Then(Folly)/64/0/real_time 8150 ns 8150 ns 85466 -Then(Folly)/0/1/real_time 23274 ns 18630 ns 29867 -Then(Folly)/1/1/real_time 23506 ns 18910 ns 30455 -Then(Folly)/2/1/real_time 23569 ns 19020 ns 24018 -Then(Folly)/4/1/real_time 24055 ns 19416 ns 29448 -Then(Folly)/8/1/real_time 26283 ns 20832 ns 28412 -Then(Folly)/16/1/real_time 26407 ns 21235 ns 27855 -Then(Folly)/32/1/real_time 29041 ns 23486 ns 25592 -Then(Folly)/64/1/real_time 37692 ns 30733 ns 18611 -Then(Folly)/0/2/real_time 24609 ns 19572 ns 30817 -Then(Folly)/1/2/real_time 23683 ns 18936 ns 30235 -Then(Folly)/2/2/real_time 24902 ns 19593 ns 29723 -Then(Folly)/4/2/real_time 24541 ns 19271 ns 29215 -Then(Folly)/8/2/real_time 28168 ns 21146 ns 27971 -Then(Folly)/16/2/real_time 28015 ns 20788 ns 25662 -Then(Folly)/32/2/real_time 33851 ns 23676 ns 22577 -Then(Folly)/64/2/real_time 47032 ns 34137 ns 14646 -Then(BoostThread)/0/0/real_time 556 ns 556 ns 1268525 -Then(BoostThread)/1/0/real_time 1202 ns 1202 ns 582523 -Then(BoostThread)/2/0/real_time 1831 ns 1831 ns 382391 -Then(BoostThread)/4/0/real_time 3078 ns 3078 ns 227256 -Then(BoostThread)/8/0/real_time 5575 ns 5575 ns 125889 -Then(BoostThread)/16/0/real_time 10568 ns 10568 ns 66568 -Then(BoostThread)/32/0/real_time 20556 ns 20556 ns 34086 -Then(BoostThread)/64/0/real_time 40419 ns 40419 ns 17289 -Then(BoostThread)/0/1/real_time 23649 ns 19064 ns 29746 -Then(BoostThread)/1/1/real_time 26033 ns 20764 ns 28260 -Then(BoostThread)/2/1/real_time 24743 ns 20492 ns 26444 -Then(BoostThread)/4/1/real_time 28977 ns 23428 ns 26203 -Then(BoostThread)/8/1/real_time 28835 ns 24649 ns 23762 -Then(BoostThread)/16/1/real_time 35420 ns 30485 ns 20764 -Then(BoostThread)/32/1/real_time 42184 ns 39244 ns 15579 -Then(BoostThread)/64/1/real_time 62011 ns 58879 ns 9817 -Then(BoostThread)/0/2/real_time 22804 ns 18540 ns 23752 -Then(BoostThread)/1/2/real_time 24603 ns 19469 ns 28101 -Then(BoostThread)/2/2/real_time 27617 ns 21262 ns 27164 -Then(BoostThread)/4/2/real_time 27392 ns 21404 ns 25431 -Then(BoostThread)/8/2/real_time 31255 ns 24553 ns 21881 -Then(BoostThread)/16/2/real_time 40047 ns 30260 ns 16978 -Then(BoostThread)/32/2/real_time 60102 ns 42451 ns 12287 -Then(BoostThread)/64/2/real_time 95847 ns 67205 ns 5980 -ComplexBlob(YACLib)/0/real_time 10438 ns 10438 ns 67453 -ComplexBlob(Folly)/0/real_time 21264 ns 21264 ns 32935 -ComplexBlob(BoostThread)/0/real_time 110336 ns 105043 ns 5947 -ComplexBlob(YACLib)/2/real_time 9433 ns 9433 ns 74691 -ComplexBlob(Folly)/2/real_time 21227 ns 21227 ns 32749 -ComplexBlob(BoostThread)/2/real_time 142007 ns 123412 ns 4863 -ComplexBlob(YACLib)/4/real_time 9611 ns 9611 ns 72343 -ComplexBlob(Folly)/4/real_time 22262 ns 22262 ns 32056 -ComplexBlob(BoostThread)/4/real_time 116186 ns 109158 ns 6344 -ComplexBlob(YACLib)/8/real_time 9430 ns 9430 ns 74025 -ComplexBlob(Folly)/8/real_time 21548 ns 21548 ns 32699 -ComplexBlob(BoostThread)/8/real_time 110599 ns 105153 ns 6055 -ComplexBlob(YACLib)/16/real_time 10799 ns 10799 ns 64225 -ComplexBlob(Folly)/16/real_time 21471 ns 21471 ns 32880 -ComplexBlob(BoostThread)/16/real_time 110121 ns 104643 ns 6396 -ComplexBlob(YACLib)/32/real_time 10889 ns 10889 ns 64492 -ComplexBlob(Folly)/32/real_time 21314 ns 21314 ns 32919 -ComplexBlob(BoostThread)/32/real_time 141307 ns 122780 ns 4945 -ComplexBlob(YACLib)/64/real_time 10057 ns 10057 ns 68993 -ComplexBlob(Folly)/64/real_time 21389 ns 21389 ns 32829 -ComplexBlob(BoostThread)/64/real_time 116505 ns 109359 ns 6337 -ComplexBlob(YACLib)/128/real_time 10871 ns 10871 ns 64530 -ComplexBlob(Folly)/128/real_time 21683 ns 21683 ns 32066 -ComplexBlob(BoostThread)/128/real_time 115472 ns 108746 ns 6221 -ComplexBlob(YACLib)/256/real_time 11054 ns 11054 ns 63596 -ComplexBlob(Folly)/256/real_time 22712 ns 22712 ns 30662 -ComplexBlob(BoostThread)/256/real_time 111918 ns 106382 ns 6314 -ComplexBlob(YACLib)/512/real_time 11673 ns 11673 ns 59016 -ComplexBlob(Folly)/512/real_time 23243 ns 23243 ns 30082 -ComplexBlob(BoostThread)/512/real_time 112342 ns 106982 ns 6184 -ComplexBlob(YACLib)/1024/real_time 19900 ns 19900 ns 35651 -ComplexBlob(Folly)/1024/real_time 29935 ns 29935 ns 23513 -ComplexBlob(BoostThread)/1024/real_time 131994 ns 125513 ns 5551 -ComplexBlob(YACLib)/2048/real_time 22301 ns 22301 ns 30860 -ComplexBlob(Folly)/2048/real_time 34580 ns 34579 ns 20406 -ComplexBlob(BoostThread)/2048/real_time 131183 ns 125555 ns 5350 -ComplexBlob(YACLib)/4096/real_time 32902 ns 32902 ns 21254 -ComplexBlob(Folly)/4096/real_time 53338 ns 53338 ns 12004 -ComplexBlob(BoostThread)/4096/real_time 140791 ns 135281 ns 4959 -ComplexBlob(YACLib)/8192/real_time 107595 ns 107594 ns 6541 -ComplexBlob(Folly)/8192/real_time 124842 ns 124841 ns 5606 -ComplexBlob(BoostThread)/8192/real_time 196978 ns 189541 ns 3614 -NoContention(YACLib)/real_time 593623 ns 289040 ns 1175 -NoContention(Folly)/real_time 1021784 ns 432992 ns 685 -NoContention(BoostThread)/real_time 3453957 ns 1723409 ns 201 -Contention(YACLib)/real_time 756586 ns 217519 ns 931 -Contention(Folly)/real_time 1694969 ns 500656 ns 412 -Contention(BoostThread)/real_time 5449775 ns 1717421 ns 131 -2022-03-30T16:57:27+03:00 -Running ./build_gcc_libstdcxx/future/future -Run on (20 X 3609.6 MHz CPU s) -CPU Caches: - L1 Data 48 KiB (x10) - L1 Instruction 32 KiB (x10) - L2 Unified 1280 KiB (x10) - L3 Unified 25600 KiB (x1) -Load Average: 0.80, 1.21, 1.14 ----------------------------------------------------------------------------------- -Benchmark Time CPU Iterations ----------------------------------------------------------------------------------- -ConstantFuture(Std)/real_time 90.7 ns 90.7 ns 7503359 -ConstantFuture(YACLib)/real_time 14.2 ns 14.2 ns 49706168 -ConstantFuture(Folly)/real_time 12.5 ns 12.5 ns 56565539 -ConstantFuture(BoostThread)/real_time 93.9 ns 93.9 ns 7414307 -PromiseAndFuture(Std)/real_time 96.6 ns 96.6 ns 7293234 -PromiseAndFuture(YACLib)/real_time 21.5 ns 21.5 ns 32570108 -PromiseAndFuture(Folly)/real_time 45.1 ns 45.1 ns 15249675 -PromiseAndFuture(BoostThread)/real_time 100 ns 100 ns 6897154 -Then(YACLib)/0/0/real_time 51.9 ns 51.9 ns 13602421 -Then(YACLib)/1/0/real_time 108 ns 108 ns 6472077 -Then(YACLib)/2/0/real_time 165 ns 165 ns 4282840 -Then(YACLib)/4/0/real_time 280 ns 280 ns 2518463 -Then(YACLib)/8/0/real_time 508 ns 508 ns 1370937 -Then(YACLib)/16/0/real_time 968 ns 968 ns 713683 -Then(YACLib)/32/0/real_time 1874 ns 1874 ns 374649 -Then(YACLib)/64/0/real_time 3673 ns 3673 ns 188754 -Then(YACLib)/0/1/real_time 24263 ns 18802 ns 30236 -Then(YACLib)/1/1/real_time 22821 ns 18186 ns 30521 -Then(YACLib)/2/1/real_time 25491 ns 19691 ns 30541 -Then(YACLib)/4/1/real_time 23095 ns 18489 ns 29607 -Then(YACLib)/8/1/real_time 25427 ns 20046 ns 29528 -Then(YACLib)/16/1/real_time 24549 ns 19907 ns 28661 -Then(YACLib)/32/1/real_time 25389 ns 21011 ns 27449 -Then(YACLib)/64/1/real_time 26678 ns 23363 ns 22885 -Then(YACLib)/0/2/real_time 22325 ns 17795 ns 30872 -Then(YACLib)/1/2/real_time 24460 ns 18890 ns 27057 -Then(YACLib)/2/2/real_time 23494 ns 18333 ns 29538 -Then(YACLib)/4/2/real_time 25406 ns 19276 ns 29069 -Then(YACLib)/8/2/real_time 23882 ns 18604 ns 28507 -Then(YACLib)/16/2/real_time 27142 ns 20662 ns 27920 -Then(YACLib)/32/2/real_time 27026 ns 20645 ns 25466 -Then(YACLib)/64/2/real_time 34960 ns 27127 ns 20507 -Then(Folly)/0/0/real_time 98.0 ns 98.0 ns 7143331 -Then(Folly)/1/0/real_time 213 ns 213 ns 3283409 -Then(Folly)/2/0/real_time 326 ns 326 ns 2155036 -Then(Folly)/4/0/real_time 564 ns 564 ns 1241127 -Then(Folly)/8/0/real_time 1021 ns 1021 ns 682203 -Then(Folly)/16/0/real_time 1936 ns 1936 ns 359192 -Then(Folly)/32/0/real_time 3766 ns 3766 ns 184719 -Then(Folly)/64/0/real_time 7432 ns 7432 ns 94865 -Then(Folly)/0/1/real_time 24573 ns 19513 ns 30339 -Then(Folly)/1/1/real_time 23239 ns 18833 ns 30059 -Then(Folly)/2/1/real_time 23300 ns 18882 ns 29808 -Then(Folly)/4/1/real_time 23204 ns 18996 ns 25386 -Then(Folly)/8/1/real_time 24516 ns 19983 ns 27914 -Then(Folly)/16/1/real_time 25144 ns 20656 ns 23857 -Then(Folly)/32/1/real_time 27605 ns 22607 ns 25477 -Then(Folly)/64/1/real_time 37133 ns 30393 ns 19311 -Then(Folly)/0/2/real_time 22725 ns 18499 ns 30175 -Then(Folly)/1/2/real_time 25233 ns 19646 ns 29819 -Then(Folly)/2/2/real_time 23747 ns 18958 ns 28752 -Then(Folly)/4/2/real_time 26162 ns 20137 ns 28644 -Then(Folly)/8/2/real_time 25465 ns 19692 ns 26806 -Then(Folly)/16/2/real_time 29647 ns 21724 ns 25117 -Then(Folly)/32/2/real_time 32171 ns 22845 ns 22048 -Then(Folly)/64/2/real_time 46493 ns 33299 ns 13858 -Then(BoostThread)/0/0/real_time 572 ns 572 ns 1226472 -Then(BoostThread)/1/0/real_time 1244 ns 1244 ns 562579 -Then(BoostThread)/2/0/real_time 1896 ns 1896 ns 369270 -Then(BoostThread)/4/0/real_time 3171 ns 3171 ns 219968 -Then(BoostThread)/8/0/real_time 5763 ns 5763 ns 122146 -Then(BoostThread)/16/0/real_time 10928 ns 10927 ns 63346 -Then(BoostThread)/32/0/real_time 21180 ns 21180 ns 32996 -Then(BoostThread)/64/0/real_time 41770 ns 41770 ns 16774 -Then(BoostThread)/0/1/real_time 23019 ns 18718 ns 28933 -Then(BoostThread)/1/1/real_time 26352 ns 21036 ns 27034 -Then(BoostThread)/2/1/real_time 25388 ns 20857 ns 27427 -Then(BoostThread)/4/1/real_time 29042 ns 23461 ns 26027 -Then(BoostThread)/8/1/real_time 29095 ns 24841 ns 23354 -Then(BoostThread)/16/1/real_time 36062 ns 31020 ns 20699 -Then(BoostThread)/32/1/real_time 42810 ns 39968 ns 16221 -Then(BoostThread)/64/1/real_time 65775 ns 61021 ns 10320 -Then(BoostThread)/0/2/real_time 23079 ns 18635 ns 29307 -Then(BoostThread)/1/2/real_time 26522 ns 20522 ns 27721 -Then(BoostThread)/2/2/real_time 25322 ns 20059 ns 26542 -Then(BoostThread)/4/2/real_time 30037 ns 22791 ns 24995 -Then(BoostThread)/8/2/real_time 30486 ns 23979 ns 22100 -Then(BoostThread)/16/2/real_time 43855 ns 31934 ns 17039 -Then(BoostThread)/32/2/real_time 58038 ns 41808 ns 10086 -Then(BoostThread)/64/2/real_time 98519 ns 67969 ns 6393 -ComplexBlob(YACLib)/0/real_time 9733 ns 9733 ns 72095 -ComplexBlob(Folly)/0/real_time 22361 ns 22361 ns 31670 -ComplexBlob(BoostThread)/0/real_time 119684 ns 114095 ns 5024 -ComplexBlob(YACLib)/2/real_time 9348 ns 9348 ns 74566 -ComplexBlob(Folly)/2/real_time 21978 ns 21978 ns 31901 -ComplexBlob(BoostThread)/2/real_time 114843 ns 109580 ns 6013 -ComplexBlob(YACLib)/4/real_time 9318 ns 9318 ns 75369 -ComplexBlob(Folly)/4/real_time 22100 ns 22100 ns 31362 -ComplexBlob(BoostThread)/4/real_time 113046 ns 107550 ns 6166 -ComplexBlob(YACLib)/8/real_time 9333 ns 9333 ns 74654 -ComplexBlob(Folly)/8/real_time 22047 ns 22047 ns 31799 -ComplexBlob(BoostThread)/8/real_time 112400 ns 107077 ns 6047 -ComplexBlob(YACLib)/16/real_time 10624 ns 10624 ns 66432 -ComplexBlob(Folly)/16/real_time 22101 ns 22101 ns 31982 -ComplexBlob(BoostThread)/16/real_time 112436 ns 106910 ns 6234 -ComplexBlob(YACLib)/32/real_time 10697 ns 10697 ns 65432 -ComplexBlob(Folly)/32/real_time 22000 ns 22000 ns 31833 -ComplexBlob(BoostThread)/32/real_time 118145 ns 111063 ns 6282 -ComplexBlob(YACLib)/64/real_time 9900 ns 9900 ns 71195 -ComplexBlob(Folly)/64/real_time 22563 ns 22563 ns 30840 -ComplexBlob(BoostThread)/64/real_time 115245 ns 109009 ns 4933 -ComplexBlob(YACLib)/128/real_time 10910 ns 10910 ns 63752 -ComplexBlob(Folly)/128/real_time 23357 ns 23357 ns 30520 -ComplexBlob(BoostThread)/128/real_time 116660 ns 110161 ns 5686 -ComplexBlob(YACLib)/256/real_time 10555 ns 10555 ns 66911 -ComplexBlob(Folly)/256/real_time 24114 ns 24114 ns 28953 -ComplexBlob(BoostThread)/256/real_time 114419 ns 108725 ns 6161 -ComplexBlob(YACLib)/512/real_time 14074 ns 14074 ns 49433 -ComplexBlob(Folly)/512/real_time 28397 ns 28397 ns 24697 -ComplexBlob(BoostThread)/512/real_time 130115 ns 123198 ns 5666 -ComplexBlob(YACLib)/1024/real_time 21134 ns 21134 ns 32936 -ComplexBlob(Folly)/1024/real_time 33142 ns 33143 ns 21140 -ComplexBlob(BoostThread)/1024/real_time 144840 ns 139380 ns 4582 -ComplexBlob(YACLib)/2048/real_time 23561 ns 23561 ns 29808 -ComplexBlob(Folly)/2048/real_time 37955 ns 37955 ns 18016 -ComplexBlob(BoostThread)/2048/real_time 156078 ns 150336 ns 4548 -ComplexBlob(YACLib)/4096/real_time 32694 ns 32694 ns 21286 -ComplexBlob(Folly)/4096/real_time 58421 ns 58421 ns 12155 -ComplexBlob(BoostThread)/4096/real_time 184223 ns 176978 ns 3970 -ComplexBlob(YACLib)/8192/real_time 105882 ns 105882 ns 6546 -ComplexBlob(Folly)/8192/real_time 132458 ns 132458 ns 5296 -ComplexBlob(BoostThread)/8192/real_time 252571 ns 245334 ns 2595 -NoContention(YACLib)/real_time 572227 ns 288557 ns 1224 -NoContention(Folly)/real_time 973385 ns 437487 ns 722 -NoContention(BoostThread)/real_time 3516074 ns 1765353 ns 197 -Contention(YACLib)/real_time 738195 ns 216684 ns 908 -Contention(Folly)/real_time 1651140 ns 501430 ns 421 +```2022-03-30T16:54:44+03:00 +Running ./build_clang_libcxx/future/future +Run on (20 X 3609.6 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x10) + L1 Instruction 32 KiB (x10) + L2 Unified 1280 KiB (x10) + L3 Unified 25600 KiB (x1) +Load Average: 0.73, 1.49, 1.20 +----------------------------------------------------------------------------- +Benchmark Time CPU Iterations +----------------------------------------------------------------------------- +ConstantFuture(Std)/real_time 31.8 ns 31.8 ns 22190553 +ConstantFuture(YACLib)/real_time 12.9 ns 12.9 ns 54425219 +PromiseAndFuture(Std)/real_time 42.0 ns 42.0 ns 16666191 +PromiseAndFuture(YACLib)/real_time 23.6 ns 23.6 ns 29757066 +Then(YACLib)/0/0/real_time 51.6 ns 51.6 ns 13814340 +Then(YACLib)/1/0/real_time 108 ns 108 ns 6449491 +Then(YACLib)/2/0/real_time 163 ns 163 ns 4338925 +Then(YACLib)/4/0/real_time 276 ns 276 ns 2537790 +Then(YACLib)/8/0/real_time 498 ns 498 ns 1404833 +Then(YACLib)/16/0/real_time 941 ns 941 ns 740001 +Then(YACLib)/32/0/real_time 1813 ns 1813 ns 385745 +Then(YACLib)/64/0/real_time 3581 ns 3581 ns 195741 +Then(YACLib)/0/1/real_time 22629 ns 17947 ns 31719 +Then(YACLib)/1/1/real_time 22633 ns 18097 ns 31213 +Then(YACLib)/2/1/real_time 22817 ns 18246 ns 24594 +Then(YACLib)/4/1/real_time 23345 ns 18675 ns 28679 +Then(YACLib)/8/1/real_time 26533 ns 20614 ns 27884 +Then(YACLib)/16/1/real_time 24612 ns 19967 ns 27022 +Then(YACLib)/32/1/real_time 27010 ns 21976 ns 27467 +Then(YACLib)/64/1/real_time 26491 ns 23182 ns 25875 +Then(YACLib)/0/2/real_time 24122 ns 18788 ns 30608 +Then(YACLib)/1/2/real_time 23173 ns 18220 ns 29999 +Then(YACLib)/2/2/real_time 25244 ns 19339 ns 29579 +Then(YACLib)/4/2/real_time 24043 ns 18557 ns 29058 +Then(YACLib)/8/2/real_time 26520 ns 19993 ns 29613 +Then(YACLib)/16/2/real_time 25730 ns 19769 ns 27449 +Then(YACLib)/32/2/real_time 30315 ns 22259 ns 25301 +Then(YACLib)/64/2/real_time 31999 ns 23949 ns 21286 +ComplexBlob(YACLib)/0/real_time 10374 ns 10374 ns 67308 +ComplexBlob(YACLib)/2/real_time 9801 ns 9801 ns 71469 +ComplexBlob(YACLib)/4/real_time 9827 ns 9827 ns 71541 +ComplexBlob(YACLib)/8/real_time 9805 ns 9805 ns 71593 +ComplexBlob(YACLib)/16/real_time 11154 ns 11154 ns 62962 +ComplexBlob(YACLib)/32/real_time 11125 ns 11125 ns 62780 +ComplexBlob(YACLib)/64/real_time 10069 ns 10069 ns 69875 +ComplexBlob(YACLib)/128/real_time 10646 ns 10646 ns 65945 +ComplexBlob(YACLib)/256/real_time 11458 ns 11458 ns 60897 +ComplexBlob(YACLib)/512/real_time 11793 ns 11793 ns 58843 +ComplexBlob(YACLib)/1024/real_time 19296 ns 19296 ns 36463 +ComplexBlob(YACLib)/2048/real_time 22367 ns 22367 ns 31275 +ComplexBlob(YACLib)/4096/real_time 32531 ns 32531 ns 21726 +ComplexBlob(YACLib)/8192/real_time 127390 ns 127390 ns 5476 +NoContention(YACLib)/real_time 601443 ns 302777 ns 1076 +Contention(YACLib)/real_time 757511 ns 214086 ns 937 +2022-03-30T16:55:25+03:00 +Running ./build_clang_libstdcxx/future/future +Run on (20 X 3609.6 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x10) + L1 Instruction 32 KiB (x10) + L2 Unified 1280 KiB (x10) + L3 Unified 25600 KiB (x1) +Load Average: 0.79, 1.40, 1.18 +---------------------------------------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------------------------------------- +ConstantFuture(Std)/real_time 91.3 ns 91.3 ns 7526855 +ConstantFuture(YACLib)/real_time 12.7 ns 12.7 ns 54310592 +ConstantFuture(Folly)/real_time 12.4 ns 12.4 ns 57598706 +ConstantFuture(BoostThread)/real_time 88.2 ns 88.2 ns 7986037 +PromiseAndFuture(Std)/real_time 96.3 ns 96.3 ns 7013497 +PromiseAndFuture(YACLib)/real_time 22.4 ns 22.4 ns 30838971 +PromiseAndFuture(Folly)/real_time 49.0 ns 49.0 ns 14279965 +PromiseAndFuture(BoostThread)/real_time 92.3 ns 92.3 ns 7542843 +Then(YACLib)/0/0/real_time 54.9 ns 54.9 ns 12825362 +Then(YACLib)/1/0/real_time 111 ns 111 ns 6207085 +Then(YACLib)/2/0/real_time 168 ns 168 ns 4195324 +Then(YACLib)/4/0/real_time 284 ns 284 ns 2479942 +Then(YACLib)/8/0/real_time 510 ns 510 ns 1360601 +Then(YACLib)/16/0/real_time 961 ns 961 ns 710419 +Then(YACLib)/32/0/real_time 1868 ns 1868 ns 376092 +Then(YACLib)/64/0/real_time 3646 ns 3646 ns 192112 +Then(YACLib)/0/1/real_time 23072 ns 18173 ns 30602 +Then(YACLib)/1/1/real_time 22938 ns 18229 ns 28529 +Then(YACLib)/2/1/real_time 22915 ns 18264 ns 24189 +Then(YACLib)/4/1/real_time 23306 ns 18642 ns 30023 +Then(YACLib)/8/1/real_time 25767 ns 20203 ns 29130 +Then(YACLib)/16/1/real_time 24621 ns 20051 ns 28781 +Then(YACLib)/32/1/real_time 27187 ns 22144 ns 27410 +Then(YACLib)/64/1/real_time 27358 ns 23541 ns 26298 +Then(YACLib)/0/2/real_time 24657 ns 19039 ns 31663 +Then(YACLib)/1/2/real_time 23234 ns 18163 ns 28119 +Then(YACLib)/2/2/real_time 25039 ns 19298 ns 28815 +Then(YACLib)/4/2/real_time 24177 ns 18656 ns 29070 +Then(YACLib)/8/2/real_time 26075 ns 19834 ns 28303 +Then(YACLib)/16/2/real_time 26422 ns 19965 ns 27358 +Then(YACLib)/32/2/real_time 27420 ns 20722 ns 25907 +Then(YACLib)/64/2/real_time 32485 ns 24018 ns 20179 +Then(Folly)/0/0/real_time 97.0 ns 97.0 ns 7167378 +Then(Folly)/1/0/real_time 217 ns 217 ns 3212924 +Then(Folly)/2/0/real_time 350 ns 350 ns 2041123 +Then(Folly)/4/0/real_time 605 ns 605 ns 1149099 +Then(Folly)/8/0/real_time 1112 ns 1112 ns 628177 +Then(Folly)/16/0/real_time 2131 ns 2131 ns 330572 +Then(Folly)/32/0/real_time 4143 ns 4143 ns 169298 +Then(Folly)/64/0/real_time 8150 ns 8150 ns 85466 +Then(Folly)/0/1/real_time 23274 ns 18630 ns 29867 +Then(Folly)/1/1/real_time 23506 ns 18910 ns 30455 +Then(Folly)/2/1/real_time 23569 ns 19020 ns 24018 +Then(Folly)/4/1/real_time 24055 ns 19416 ns 29448 +Then(Folly)/8/1/real_time 26283 ns 20832 ns 28412 +Then(Folly)/16/1/real_time 26407 ns 21235 ns 27855 +Then(Folly)/32/1/real_time 29041 ns 23486 ns 25592 +Then(Folly)/64/1/real_time 37692 ns 30733 ns 18611 +Then(Folly)/0/2/real_time 24609 ns 19572 ns 30817 +Then(Folly)/1/2/real_time 23683 ns 18936 ns 30235 +Then(Folly)/2/2/real_time 24902 ns 19593 ns 29723 +Then(Folly)/4/2/real_time 24541 ns 19271 ns 29215 +Then(Folly)/8/2/real_time 28168 ns 21146 ns 27971 +Then(Folly)/16/2/real_time 28015 ns 20788 ns 25662 +Then(Folly)/32/2/real_time 33851 ns 23676 ns 22577 +Then(Folly)/64/2/real_time 47032 ns 34137 ns 14646 +Then(BoostThread)/0/0/real_time 556 ns 556 ns 1268525 +Then(BoostThread)/1/0/real_time 1202 ns 1202 ns 582523 +Then(BoostThread)/2/0/real_time 1831 ns 1831 ns 382391 +Then(BoostThread)/4/0/real_time 3078 ns 3078 ns 227256 +Then(BoostThread)/8/0/real_time 5575 ns 5575 ns 125889 +Then(BoostThread)/16/0/real_time 10568 ns 10568 ns 66568 +Then(BoostThread)/32/0/real_time 20556 ns 20556 ns 34086 +Then(BoostThread)/64/0/real_time 40419 ns 40419 ns 17289 +Then(BoostThread)/0/1/real_time 23649 ns 19064 ns 29746 +Then(BoostThread)/1/1/real_time 26033 ns 20764 ns 28260 +Then(BoostThread)/2/1/real_time 24743 ns 20492 ns 26444 +Then(BoostThread)/4/1/real_time 28977 ns 23428 ns 26203 +Then(BoostThread)/8/1/real_time 28835 ns 24649 ns 23762 +Then(BoostThread)/16/1/real_time 35420 ns 30485 ns 20764 +Then(BoostThread)/32/1/real_time 42184 ns 39244 ns 15579 +Then(BoostThread)/64/1/real_time 62011 ns 58879 ns 9817 +Then(BoostThread)/0/2/real_time 22804 ns 18540 ns 23752 +Then(BoostThread)/1/2/real_time 24603 ns 19469 ns 28101 +Then(BoostThread)/2/2/real_time 27617 ns 21262 ns 27164 +Then(BoostThread)/4/2/real_time 27392 ns 21404 ns 25431 +Then(BoostThread)/8/2/real_time 31255 ns 24553 ns 21881 +Then(BoostThread)/16/2/real_time 40047 ns 30260 ns 16978 +Then(BoostThread)/32/2/real_time 60102 ns 42451 ns 12287 +Then(BoostThread)/64/2/real_time 95847 ns 67205 ns 5980 +ComplexBlob(YACLib)/0/real_time 10438 ns 10438 ns 67453 +ComplexBlob(Folly)/0/real_time 21264 ns 21264 ns 32935 +ComplexBlob(BoostThread)/0/real_time 110336 ns 105043 ns 5947 +ComplexBlob(YACLib)/2/real_time 9433 ns 9433 ns 74691 +ComplexBlob(Folly)/2/real_time 21227 ns 21227 ns 32749 +ComplexBlob(BoostThread)/2/real_time 142007 ns 123412 ns 4863 +ComplexBlob(YACLib)/4/real_time 9611 ns 9611 ns 72343 +ComplexBlob(Folly)/4/real_time 22262 ns 22262 ns 32056 +ComplexBlob(BoostThread)/4/real_time 116186 ns 109158 ns 6344 +ComplexBlob(YACLib)/8/real_time 9430 ns 9430 ns 74025 +ComplexBlob(Folly)/8/real_time 21548 ns 21548 ns 32699 +ComplexBlob(BoostThread)/8/real_time 110599 ns 105153 ns 6055 +ComplexBlob(YACLib)/16/real_time 10799 ns 10799 ns 64225 +ComplexBlob(Folly)/16/real_time 21471 ns 21471 ns 32880 +ComplexBlob(BoostThread)/16/real_time 110121 ns 104643 ns 6396 +ComplexBlob(YACLib)/32/real_time 10889 ns 10889 ns 64492 +ComplexBlob(Folly)/32/real_time 21314 ns 21314 ns 32919 +ComplexBlob(BoostThread)/32/real_time 141307 ns 122780 ns 4945 +ComplexBlob(YACLib)/64/real_time 10057 ns 10057 ns 68993 +ComplexBlob(Folly)/64/real_time 21389 ns 21389 ns 32829 +ComplexBlob(BoostThread)/64/real_time 116505 ns 109359 ns 6337 +ComplexBlob(YACLib)/128/real_time 10871 ns 10871 ns 64530 +ComplexBlob(Folly)/128/real_time 21683 ns 21683 ns 32066 +ComplexBlob(BoostThread)/128/real_time 115472 ns 108746 ns 6221 +ComplexBlob(YACLib)/256/real_time 11054 ns 11054 ns 63596 +ComplexBlob(Folly)/256/real_time 22712 ns 22712 ns 30662 +ComplexBlob(BoostThread)/256/real_time 111918 ns 106382 ns 6314 +ComplexBlob(YACLib)/512/real_time 11673 ns 11673 ns 59016 +ComplexBlob(Folly)/512/real_time 23243 ns 23243 ns 30082 +ComplexBlob(BoostThread)/512/real_time 112342 ns 106982 ns 6184 +ComplexBlob(YACLib)/1024/real_time 19900 ns 19900 ns 35651 +ComplexBlob(Folly)/1024/real_time 29935 ns 29935 ns 23513 +ComplexBlob(BoostThread)/1024/real_time 131994 ns 125513 ns 5551 +ComplexBlob(YACLib)/2048/real_time 22301 ns 22301 ns 30860 +ComplexBlob(Folly)/2048/real_time 34580 ns 34579 ns 20406 +ComplexBlob(BoostThread)/2048/real_time 131183 ns 125555 ns 5350 +ComplexBlob(YACLib)/4096/real_time 32902 ns 32902 ns 21254 +ComplexBlob(Folly)/4096/real_time 53338 ns 53338 ns 12004 +ComplexBlob(BoostThread)/4096/real_time 140791 ns 135281 ns 4959 +ComplexBlob(YACLib)/8192/real_time 107595 ns 107594 ns 6541 +ComplexBlob(Folly)/8192/real_time 124842 ns 124841 ns 5606 +ComplexBlob(BoostThread)/8192/real_time 196978 ns 189541 ns 3614 +NoContention(YACLib)/real_time 593623 ns 289040 ns 1175 +NoContention(Folly)/real_time 1021784 ns 432992 ns 685 +NoContention(BoostThread)/real_time 3453957 ns 1723409 ns 201 +Contention(YACLib)/real_time 756586 ns 217519 ns 931 +Contention(Folly)/real_time 1694969 ns 500656 ns 412 +Contention(BoostThread)/real_time 5449775 ns 1717421 ns 131 +2022-03-30T16:57:27+03:00 +Running ./build_gcc_libstdcxx/future/future +Run on (20 X 3609.6 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x10) + L1 Instruction 32 KiB (x10) + L2 Unified 1280 KiB (x10) + L3 Unified 25600 KiB (x1) +Load Average: 0.80, 1.21, 1.14 +---------------------------------------------------------------------------------- +Benchmark Time CPU Iterations +---------------------------------------------------------------------------------- +ConstantFuture(Std)/real_time 90.7 ns 90.7 ns 7503359 +ConstantFuture(YACLib)/real_time 14.2 ns 14.2 ns 49706168 +ConstantFuture(Folly)/real_time 12.5 ns 12.5 ns 56565539 +ConstantFuture(BoostThread)/real_time 93.9 ns 93.9 ns 7414307 +PromiseAndFuture(Std)/real_time 96.6 ns 96.6 ns 7293234 +PromiseAndFuture(YACLib)/real_time 21.5 ns 21.5 ns 32570108 +PromiseAndFuture(Folly)/real_time 45.1 ns 45.1 ns 15249675 +PromiseAndFuture(BoostThread)/real_time 100 ns 100 ns 6897154 +Then(YACLib)/0/0/real_time 51.9 ns 51.9 ns 13602421 +Then(YACLib)/1/0/real_time 108 ns 108 ns 6472077 +Then(YACLib)/2/0/real_time 165 ns 165 ns 4282840 +Then(YACLib)/4/0/real_time 280 ns 280 ns 2518463 +Then(YACLib)/8/0/real_time 508 ns 508 ns 1370937 +Then(YACLib)/16/0/real_time 968 ns 968 ns 713683 +Then(YACLib)/32/0/real_time 1874 ns 1874 ns 374649 +Then(YACLib)/64/0/real_time 3673 ns 3673 ns 188754 +Then(YACLib)/0/1/real_time 24263 ns 18802 ns 30236 +Then(YACLib)/1/1/real_time 22821 ns 18186 ns 30521 +Then(YACLib)/2/1/real_time 25491 ns 19691 ns 30541 +Then(YACLib)/4/1/real_time 23095 ns 18489 ns 29607 +Then(YACLib)/8/1/real_time 25427 ns 20046 ns 29528 +Then(YACLib)/16/1/real_time 24549 ns 19907 ns 28661 +Then(YACLib)/32/1/real_time 25389 ns 21011 ns 27449 +Then(YACLib)/64/1/real_time 26678 ns 23363 ns 22885 +Then(YACLib)/0/2/real_time 22325 ns 17795 ns 30872 +Then(YACLib)/1/2/real_time 24460 ns 18890 ns 27057 +Then(YACLib)/2/2/real_time 23494 ns 18333 ns 29538 +Then(YACLib)/4/2/real_time 25406 ns 19276 ns 29069 +Then(YACLib)/8/2/real_time 23882 ns 18604 ns 28507 +Then(YACLib)/16/2/real_time 27142 ns 20662 ns 27920 +Then(YACLib)/32/2/real_time 27026 ns 20645 ns 25466 +Then(YACLib)/64/2/real_time 34960 ns 27127 ns 20507 +Then(Folly)/0/0/real_time 98.0 ns 98.0 ns 7143331 +Then(Folly)/1/0/real_time 213 ns 213 ns 3283409 +Then(Folly)/2/0/real_time 326 ns 326 ns 2155036 +Then(Folly)/4/0/real_time 564 ns 564 ns 1241127 +Then(Folly)/8/0/real_time 1021 ns 1021 ns 682203 +Then(Folly)/16/0/real_time 1936 ns 1936 ns 359192 +Then(Folly)/32/0/real_time 3766 ns 3766 ns 184719 +Then(Folly)/64/0/real_time 7432 ns 7432 ns 94865 +Then(Folly)/0/1/real_time 24573 ns 19513 ns 30339 +Then(Folly)/1/1/real_time 23239 ns 18833 ns 30059 +Then(Folly)/2/1/real_time 23300 ns 18882 ns 29808 +Then(Folly)/4/1/real_time 23204 ns 18996 ns 25386 +Then(Folly)/8/1/real_time 24516 ns 19983 ns 27914 +Then(Folly)/16/1/real_time 25144 ns 20656 ns 23857 +Then(Folly)/32/1/real_time 27605 ns 22607 ns 25477 +Then(Folly)/64/1/real_time 37133 ns 30393 ns 19311 +Then(Folly)/0/2/real_time 22725 ns 18499 ns 30175 +Then(Folly)/1/2/real_time 25233 ns 19646 ns 29819 +Then(Folly)/2/2/real_time 23747 ns 18958 ns 28752 +Then(Folly)/4/2/real_time 26162 ns 20137 ns 28644 +Then(Folly)/8/2/real_time 25465 ns 19692 ns 26806 +Then(Folly)/16/2/real_time 29647 ns 21724 ns 25117 +Then(Folly)/32/2/real_time 32171 ns 22845 ns 22048 +Then(Folly)/64/2/real_time 46493 ns 33299 ns 13858 +Then(BoostThread)/0/0/real_time 572 ns 572 ns 1226472 +Then(BoostThread)/1/0/real_time 1244 ns 1244 ns 562579 +Then(BoostThread)/2/0/real_time 1896 ns 1896 ns 369270 +Then(BoostThread)/4/0/real_time 3171 ns 3171 ns 219968 +Then(BoostThread)/8/0/real_time 5763 ns 5763 ns 122146 +Then(BoostThread)/16/0/real_time 10928 ns 10927 ns 63346 +Then(BoostThread)/32/0/real_time 21180 ns 21180 ns 32996 +Then(BoostThread)/64/0/real_time 41770 ns 41770 ns 16774 +Then(BoostThread)/0/1/real_time 23019 ns 18718 ns 28933 +Then(BoostThread)/1/1/real_time 26352 ns 21036 ns 27034 +Then(BoostThread)/2/1/real_time 25388 ns 20857 ns 27427 +Then(BoostThread)/4/1/real_time 29042 ns 23461 ns 26027 +Then(BoostThread)/8/1/real_time 29095 ns 24841 ns 23354 +Then(BoostThread)/16/1/real_time 36062 ns 31020 ns 20699 +Then(BoostThread)/32/1/real_time 42810 ns 39968 ns 16221 +Then(BoostThread)/64/1/real_time 65775 ns 61021 ns 10320 +Then(BoostThread)/0/2/real_time 23079 ns 18635 ns 29307 +Then(BoostThread)/1/2/real_time 26522 ns 20522 ns 27721 +Then(BoostThread)/2/2/real_time 25322 ns 20059 ns 26542 +Then(BoostThread)/4/2/real_time 30037 ns 22791 ns 24995 +Then(BoostThread)/8/2/real_time 30486 ns 23979 ns 22100 +Then(BoostThread)/16/2/real_time 43855 ns 31934 ns 17039 +Then(BoostThread)/32/2/real_time 58038 ns 41808 ns 10086 +Then(BoostThread)/64/2/real_time 98519 ns 67969 ns 6393 +ComplexBlob(YACLib)/0/real_time 9733 ns 9733 ns 72095 +ComplexBlob(Folly)/0/real_time 22361 ns 22361 ns 31670 +ComplexBlob(BoostThread)/0/real_time 119684 ns 114095 ns 5024 +ComplexBlob(YACLib)/2/real_time 9348 ns 9348 ns 74566 +ComplexBlob(Folly)/2/real_time 21978 ns 21978 ns 31901 +ComplexBlob(BoostThread)/2/real_time 114843 ns 109580 ns 6013 +ComplexBlob(YACLib)/4/real_time 9318 ns 9318 ns 75369 +ComplexBlob(Folly)/4/real_time 22100 ns 22100 ns 31362 +ComplexBlob(BoostThread)/4/real_time 113046 ns 107550 ns 6166 +ComplexBlob(YACLib)/8/real_time 9333 ns 9333 ns 74654 +ComplexBlob(Folly)/8/real_time 22047 ns 22047 ns 31799 +ComplexBlob(BoostThread)/8/real_time 112400 ns 107077 ns 6047 +ComplexBlob(YACLib)/16/real_time 10624 ns 10624 ns 66432 +ComplexBlob(Folly)/16/real_time 22101 ns 22101 ns 31982 +ComplexBlob(BoostThread)/16/real_time 112436 ns 106910 ns 6234 +ComplexBlob(YACLib)/32/real_time 10697 ns 10697 ns 65432 +ComplexBlob(Folly)/32/real_time 22000 ns 22000 ns 31833 +ComplexBlob(BoostThread)/32/real_time 118145 ns 111063 ns 6282 +ComplexBlob(YACLib)/64/real_time 9900 ns 9900 ns 71195 +ComplexBlob(Folly)/64/real_time 22563 ns 22563 ns 30840 +ComplexBlob(BoostThread)/64/real_time 115245 ns 109009 ns 4933 +ComplexBlob(YACLib)/128/real_time 10910 ns 10910 ns 63752 +ComplexBlob(Folly)/128/real_time 23357 ns 23357 ns 30520 +ComplexBlob(BoostThread)/128/real_time 116660 ns 110161 ns 5686 +ComplexBlob(YACLib)/256/real_time 10555 ns 10555 ns 66911 +ComplexBlob(Folly)/256/real_time 24114 ns 24114 ns 28953 +ComplexBlob(BoostThread)/256/real_time 114419 ns 108725 ns 6161 +ComplexBlob(YACLib)/512/real_time 14074 ns 14074 ns 49433 +ComplexBlob(Folly)/512/real_time 28397 ns 28397 ns 24697 +ComplexBlob(BoostThread)/512/real_time 130115 ns 123198 ns 5666 +ComplexBlob(YACLib)/1024/real_time 21134 ns 21134 ns 32936 +ComplexBlob(Folly)/1024/real_time 33142 ns 33143 ns 21140 +ComplexBlob(BoostThread)/1024/real_time 144840 ns 139380 ns 4582 +ComplexBlob(YACLib)/2048/real_time 23561 ns 23561 ns 29808 +ComplexBlob(Folly)/2048/real_time 37955 ns 37955 ns 18016 +ComplexBlob(BoostThread)/2048/real_time 156078 ns 150336 ns 4548 +ComplexBlob(YACLib)/4096/real_time 32694 ns 32694 ns 21286 +ComplexBlob(Folly)/4096/real_time 58421 ns 58421 ns 12155 +ComplexBlob(BoostThread)/4096/real_time 184223 ns 176978 ns 3970 +ComplexBlob(YACLib)/8192/real_time 105882 ns 105882 ns 6546 +ComplexBlob(Folly)/8192/real_time 132458 ns 132458 ns 5296 +ComplexBlob(BoostThread)/8192/real_time 252571 ns 245334 ns 2595 +NoContention(YACLib)/real_time 572227 ns 288557 ns 1224 +NoContention(Folly)/real_time 973385 ns 437487 ns 722 +NoContention(BoostThread)/real_time 3516074 ns 1765353 ns 197 +Contention(YACLib)/real_time 738195 ns 216684 ns 908 +Contention(Folly)/real_time 1651140 ns 501430 ns 421 Contention(BoostThread)/real_time 5550196 ns 1724216 ns 128``` \ No newline at end of file diff --git a/future/result/i7-12700KF/clang_libcxx/data.json b/test/future/result/i7-12700KF/clang_libcxx/data.json similarity index 100% rename from future/result/i7-12700KF/clang_libcxx/data.json rename to test/future/result/i7-12700KF/clang_libcxx/data.json diff --git a/future/result/i7-12700KF/clang_libstdcxx/data.json b/test/future/result/i7-12700KF/clang_libstdcxx/data.json similarity index 100% rename from future/result/i7-12700KF/clang_libstdcxx/data.json rename to test/future/result/i7-12700KF/clang_libstdcxx/data.json diff --git a/future/result/i7-12700KF/gcc_libstdcxx/data.json b/test/future/result/i7-12700KF/gcc_libstdcxx/data.json similarity index 100% rename from future/result/i7-12700KF/gcc_libstdcxx/data.json rename to test/future/result/i7-12700KF/gcc_libstdcxx/data.json diff --git a/util/bench.hpp b/util/bench.hpp index 25aa09f..a4ca98a 100644 --- a/util/bench.hpp +++ b/util/bench.hpp @@ -1,23 +1,28 @@ #pragma once +#include #include #include #define BENCH2(name, library, arg) \ BENCHMARK_PRIVATE_DECLARE(name) = (::benchmark::internal::RegisterBenchmarkInternal( \ - new ::benchmark::internal::FunctionBenchmark(#name "(" #library ")/" #arg, name))) + new ::benchmark::internal::FunctionBenchmark(#name "/" #library "/" #arg, name))) #define BENCH1(name, library) \ BENCHMARK_PRIVATE_DECLARE(name) = (::benchmark::internal::RegisterBenchmarkInternal( \ - new ::benchmark::internal::FunctionBenchmark(#name "(" #library ")", name))) + new ::benchmark::internal::FunctionBenchmark(#name "/" #library, name))) #define GET_MACRO(_1, _2, _3, NAME, ...) NAME #define BENCH(...) GET_MACRO(__VA_ARGS__, BENCH2, BENCH1)(__VA_ARGS__) struct EmptyBenchmark { - constexpr const EmptyBenchmark* ArgsProduct(const std::initializer_list>&) const { + constexpr const EmptyBenchmark* ArgsProduct(const std::initializer_list>&) const { + return this; + } + + constexpr const EmptyBenchmark* Args(const std::initializer_list&) const { return this; } @@ -40,6 +45,10 @@ struct EmptyBenchmark { constexpr const EmptyBenchmark* ThreadPerCpu() const { return this; } + + constexpr const EmptyBenchmark* Iterations(std::int64_t) const { + return this; + } }; static constexpr EmptyBenchmark kEmptyBenchmark; @@ -48,37 +57,49 @@ static constexpr EmptyBenchmark kEmptyBenchmark; static constexpr const EmptyBenchmark* BENCHMARK_PRIVATE_NAME(name) = std::addressof(kEmptyBenchmark) #ifdef STD_ENABLE -#define BENCH_STD(name, ...) BENCH(name, Std, ##__VA_ARGS__) +# define BENCH_STD(name, ...) BENCH(name, Std, ##__VA_ARGS__) #else -#define BENCH_STD(name, ...) EMPTY_BENCH(name) +# define BENCH_STD(name, ...) EMPTY_BENCH(name) #endif #ifdef YACLIB_ENABLE -#define BENCH_YACLIB(name, ...) BENCH(name, YACLib, ##__VA_ARGS__) +# define BENCH_YACLIB(name, ...) BENCH(name, YACLib, ##__VA_ARGS__) #else -#define BENCH_YACLIB(name, ...) EMPTY_BENCH(name) +# define BENCH_YACLIB(name, ...) EMPTY_BENCH(name) +#endif + +#ifdef CPPCORO_ENABLE +# define BENCH_CPPCORO(name, ...) BENCH(name, CppCoro, ##__VA_ARGS__) +#else +# define BENCH_CPPCORO(name, ...) EMPTY_BENCH(name) #endif #ifdef FOLLY_ENABLE -#define BENCH_FOLLY(name, ...) BENCH(name, Folly, ##__VA_ARGS__) +# define BENCH_FOLLY(name, ...) BENCH(name, Folly, ##__VA_ARGS__) +#else +# define BENCH_FOLLY(name, ...) EMPTY_BENCH(name) +#endif + +#ifdef ARANGODB_ENABLE +# define BENCH_ARANGODB(name, ...) BENCH(name, ArangoDB, ##__VA_ARGS__) #else -#define BENCH_FOLLY(name, ...) EMPTY_BENCH(name) +# define BENCH_ARANGODB(name, ...) EMPTY_BENCH(name) #endif #ifdef QT_ENABLE -#define BENCH_QT(name, ...) BENCH(name, QT, ##__VA_ARGS__) +# define BENCH_QT(name, ...) BENCH(name, QT, ##__VA_ARGS__) #else -#define BENCH_QT(name, ...) EMPTY_BENCH(name) +# define BENCH_QT(name, ...) EMPTY_BENCH(name) #endif #ifdef BOOST_THREAD_ENABLE -#define BENCH_BOOST_THREAD(name, ...) BENCH(name, BoostThread, ##__VA_ARGS__) +# define BENCH_BOOST_THREAD(name, ...) BENCH(name, BoostThread, ##__VA_ARGS__) #else -#define BENCH_BOOST_THREAD(name, ...) EMPTY_BENCH(name) +# define BENCH_BOOST_THREAD(name, ...) EMPTY_BENCH(name) #endif #ifdef EXPERIMENTAL_ENABLE -#define BENCH_EXPERIMENTAL(name, ...) BENCH(name, Experimental, ##__VA_ARGS__) +# define BENCH_EXPERIMENTAL(name, ...) BENCH(name, Experimental, ##__VA_ARGS__) #else -#define BENCH_EXPERIMENTAL(name, ...) EMPTY_BENCH(name) +# define BENCH_EXPERIMENTAL(name, ...) EMPTY_BENCH(name) #endif diff --git a/util/blob.hpp b/util/blob.hpp new file mode 100644 index 0000000..e3177a3 --- /dev/null +++ b/util/blob.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace bench { + +template +struct Blob { + std::byte buffer[Size]; +}; + +} // namespace bench diff --git a/vendor/arangodb/futures/Exceptions.h b/vendor/arangodb/futures/Exceptions.h new file mode 100644 index 0000000..cf940e4 --- /dev/null +++ b/vendor/arangodb/futures/Exceptions.h @@ -0,0 +1,67 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2022 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Simon Grätzer +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +namespace arangodb { +namespace futures { + +enum class ErrorCode : uint8_t { + BrokenPromise = 1, + FutureAlreadyRetrieved = 2, + FutureNotReady = 3, + PromiseAlreadySatisfied = 4, + NoState = 5 +}; + +struct FutureException : public std::exception { + explicit FutureException(ErrorCode code) : _code(code) {} + + ErrorCode code() const noexcept { return _code; } + + char const* what() const noexcept override { + switch (_code) { + case ErrorCode::BrokenPromise: + return "Promise abandoned the shared state"; + case ErrorCode::FutureAlreadyRetrieved: + return "Future was already retrieved"; + case ErrorCode::FutureNotReady: + return "Future is not ready"; + case ErrorCode::PromiseAlreadySatisfied: + return "Promise was already satisfied"; + case ErrorCode::NoState: + return "No shared state"; + default: + return "invalid future exception"; + } + } + + private: + ErrorCode _code; +}; + +} // namespace futures +} // namespace arangodb diff --git a/vendor/arangodb/futures/Future.cpp b/vendor/arangodb/futures/Future.cpp new file mode 100644 index 0000000..397e7bf --- /dev/null +++ b/vendor/arangodb/futures/Future.cpp @@ -0,0 +1,31 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2022 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Simon Grätzer +//////////////////////////////////////////////////////////////////////////////// + +#include + +namespace arangodb::futures { +/// Make a complete void future +Future makeFuture() { + return {unit}; +} +} // namespace arangodb::futures diff --git a/vendor/arangodb/futures/Future.h b/vendor/arangodb/futures/Future.h new file mode 100644 index 0000000..808db87 --- /dev/null +++ b/vendor/arangodb/futures/Future.h @@ -0,0 +1,527 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2022 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Simon Grätzer +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace arangodb { +namespace futures { + +template +class Future; + +template +class Promise; + +template +struct isFuture { + static constexpr bool value = false; + typedef typename lift_unit::type inner; +}; + +template +struct isFuture> { + static constexpr bool value = true; + // typedef T inner; + typedef typename lift_unit::type inner; +}; + +namespace detail { + +template +struct ArgType; + +template +struct ArgType { + typedef Arg FirstArg; +}; + +template <> +struct ArgType<> { + typedef void FirstArg; +}; + +template +struct argResult { + using ArgList = ArgType; + using Result = typename std::invoke_result::type; +}; + +template +struct tryCallableResult { + typedef detail::argResult&&> Arg; + typedef isFuture ReturnsFuture; + typedef typename Arg::ArgList::FirstArg FirstArg; + typedef Future FutureT; +}; + +template +struct valueCallableResult { + typedef detail::argResult Arg; + typedef isFuture ReturnsFuture; + typedef typename Arg::ArgList::FirstArg FirstArg; + typedef Future FutureT; +}; + +template ::type> +typename std::enable_if::value, Try>::type makeTryWith(F&& func) noexcept { + try { + return Try(std::in_place, func()); + } catch (...) { + return Try(std::current_exception()); + } +} + +template ::type> +typename std::enable_if::value, Try>::type makeTryWith(F&& func) noexcept { + try { + func(); + return Try(unit); + } catch (...) { + return Try(std::current_exception()); + } +} + +// decay variant that does not remove cv-qualifier +template +struct decay { + private: + typedef typename std::remove_reference::type U; + + public: + typedef typename std::conditional::value, typename std::add_pointer::type, T>::type type; +}; + +template +using decay_t = typename decay::type; + +struct EmptyConstructor {}; + +// uses a condition_variable to wait +template +void waitImpl(Future& f) { + if (f.isReady()) { + return; // short-circuit + } + + std::mutex m; + std::condition_variable cv; + + Promise p; + Future ret = p.getFuture(); + std::move(f).thenFinal([p(std::move(p)), &cv, &m](Try&& t) mutable { + // We need to hold this mutex, while sending the notify. + // Otherwise the future ret may be ready and thereby leaving this function + // which would free the condtion variable, before sending notify. + // This is one of the rare cases where notify without lock would cause + // undefined behaviour. + std::lock_guard guard(m); + p.setTry(std::move(t)); + cv.notify_one(); + }); + std::unique_lock lock(m); + cv.wait(lock, [&ret] { + return ret.isReady(); + }); + f = std::move(ret); +} + +} // namespace detail + +/// Simple Future library based on Facebooks Folly +template +class Future { + static_assert(!std::is_same::value, "use futures::Unit instead of void"); + + friend class Promise; + template + friend Future makeFuture(Try&&); + friend Future makeFuture(); + + public: + /// @brief value type of the future + typedef T value_type; + + /// @brief Constructs a Future with no shared state. + static Future makeEmpty() { + return Future(detail::EmptyConstructor{}); + } + + // Construct a Future from a value (perfect forwarding) + template ::value && + !isFuture::type>::value>::type> + /* implicit */ Future(T2&& val) : _state(detail::SharedState::make(Try(std::forward(val)))) { + } + + // Construct a (logical) Future-of-void. + // cppcheck-ignore noExplicitConstructor + template + /* implicit */ Future(typename std::enable_if::value>::type* p = nullptr) + : _state(detail::SharedState::make(Try())) { + } + + // Construct a Future from a `T` constructed from `args` + template ::value, int>::type = 0> + explicit Future(std::in_place_t, Args&&... args) + : _state(detail::SharedState::make(std::in_place, std::forward(args)...)) { + } + + Future(Future const& o) = delete; + Future(Future&& o) noexcept : _state(std::move(o._state)) { + o._state = nullptr; + } + Future& operator=(Future const&) = delete; + Future& operator=(Future&& o) noexcept { + if (this != &o) { + detach(); + std::swap(_state, o._state); + assert(o._state == nullptr); + } + return *this; + } + + ~Future() { + detach(); + } + + /// is there a shared state set + bool valid() const noexcept { + return _state != nullptr; + } + + // True when the result (or exception) is ready + bool isReady() const { + return getState().hasResult(); + } + + /// True if the future already has a callback set + bool hasCallback() const { + return getState().hasCallback(); + } + + /// True if the result is a value (not an exception) + bool hasValue() const { + assert(isReady()); + return result().hasValue(); + } + + /// True if the result is an exception (not a value) on a future + bool hasException() const { + assert(isReady()); + return result().hasException(); + } + + // template ::value, + // std::enable_if_t = 0> + T& get() & { + wait(); + return result().get(); + } + + /// waits and moves the result out + T get() && { + wait(); + return Future(std::move(*this)).result().get(); + } + + /// Blocks until the future is fulfilled. Returns the Try of the result + Try& getTry() & { + wait(); + return getStateTryChecked(); + } + + Try&& getTry() && { + wait(); + return std::move(getStateTryChecked()); + } + + /// Returns a reference to the result's Try if it is ready, with a reference + /// category and const-qualification like those of the future. + /// Does not `wait()`; see `get()` for that. + Try& result() & { + return getStateTryChecked(); + } + Try const& result() const& { + return getStateTryChecked(); + } + Try&& result() && { + return std::move(getStateTryChecked()); + } + Try const&& result() const&& { + return std::move(getStateTryChecked()); + } + + /// Blocks until this Future is complete. + void wait() { + detail::waitImpl(*this); + } + + /// When this Future has completed, execute func which is a function that + /// can be called with either `T&&` or `Try&&`. + /// + /// Func shall return either another Future or a value. + /// + /// A Future for the return type of func is returned. + /// + /// Future f2 = f1.then([](Try&&) { return string("foo"); }); + /// + /// Preconditions: + /// + /// - `valid() == true` (else throws FutureException(ErrorCode::NoState))) + /// + /// Postconditions: + /// + /// - Calling code should act as if `valid() == false`, + /// i.e., as if `*this` was moved into RESULT. + /// - `RESULT.valid() == true` + + /// Variant: callable accepts T&&, returns value + /// e.g. f.then([](T&& t){ return t; }); + template > + typename std::enable_if::value && !R::ReturnsFuture::value, typename R::FutureT>::type + thenValue(F&& fn) && { + typedef typename R::ReturnsFuture::inner B; + using DF = detail::decay_t; + + static_assert(!isFuture::value, ""); + static_assert(!std::is_same::value, ""); + static_assert(!R::ReturnsFuture::value, ""); + + Promise promise; + auto future = promise.getFuture(); + getState().setCallback([fn = std::forward(fn), pr = std::move(promise)](Try&& t) mutable { + if (t.hasException()) { + pr.setException(std::move(t).exception()); + } else { + pr.setTry(detail::makeTryWith([&fn, &t] { + return std::invoke(std::forward(fn), std::move(t).get()); + })); + } + }); + return future; + } + + /// Variant: callable accepts T&&, returns future + /// e.g. f.then([](T&& t){ return makeFuture(t); }); + template > + typename std::enable_if::value && R::ReturnsFuture::value, typename R::FutureT>::type + thenValue(F&& fn) && { + typedef typename R::ReturnsFuture::inner B; + using DF = detail::decay_t; + + static_assert(!isFuture::value, ""); + static_assert(std::is_invocable_r, F, T>::value, "Function must be invocable with T"); + + Promise promise; + auto future = promise.getFuture(); + getState().setCallback([fn = std::forward(fn), pr = std::move(promise)](Try&& t) mutable { + if (t.hasException()) { + pr.setException(std::move(t).exception()); + } else { + try { + auto f = std::invoke(std::forward(fn), std::move(t).get()); + std::move(f).then([pr = std::move(pr)](Try&& t) mutable { + pr.setTry(std::move(t)); + }); + } catch (...) { + pr.setException(std::current_exception()); + } + } + }); + return future; + } + + /// Variant: callable accepts Try, returns value + /// e.g. f.then([](T&& t){ return t; }); + template > + typename std::enable_if::value && !R::ReturnsFuture::value, typename R::FutureT>::type + then(F&& func) && { + typedef typename R::ReturnsFuture::inner B; + using DF = detail::decay_t; + + static_assert(!isFuture::value, ""); + static_assert(!std::is_same::value, ""); + + Promise promise; + auto future = promise.getFuture(); + getState().setCallback([fn = std::forward(func), pr = std::move(promise)](Try&& t) mutable { + pr.setTry(detail::makeTryWith([&fn, &t] { + return std::invoke(std::forward(fn), std::move(t)); + })); + }); + return future; + } + + /// Variant: callable accepts Try, returns future + /// e.g. f.then([](T&& t){ return makeFuture(t); }); + template > + typename std::enable_if::value && R::ReturnsFuture::value, typename R::FutureT>::type + then(F&& func) && { + typedef typename R::ReturnsFuture::inner B; + static_assert(!isFuture::value, ""); + + Promise promise; + auto future = promise.getFuture(); + getState().setCallback([fn = std::forward(func), pr = std::move(promise)](Try&& t) mutable { + try { + auto f = std::invoke(std::forward(fn), std::move(t)); + std::move(f).then([pr = std::move(pr)](Try&& t) mutable { + pr.setTry(std::move(t)); + }); + } catch (...) { + pr.setException(std::current_exception()); + } + }); + return future; + } + + /// Variant: function returns void and accepts Try&& + /// When this Future has completed, execute func which is a function that + /// can be called with either `T&&` or `Try&&`. + template &&>::type> + typename std::enable_if::value>::type thenFinal(F&& fn) && { + getState().setCallback(std::forward>(fn)); + } + + /// Set an error continuation for this Future where the continuation can + /// be called with a known exception type and returns a `T` + template > + typename std::enable_if::value, Future::type>>::type thenError(F&& func) && { + typedef typename lift_unit::type B; + typedef std::decay_t ET; + using DF = detail::decay_t; + + Promise promise; + auto future = promise.getFuture(); + getState().setCallback([fn = std::forward(func), pr = std::move(promise)](Try&& t) mutable { + if (t.hasException()) { + try { + std::rethrow_exception(std::move(t).exception()); + } catch (ET& e) { + pr.setTry(detail::makeTryWith([&fn, &e]() mutable { + return std::invoke(std::forward(fn), e); + })); + } catch (...) { + pr.setException(std::current_exception()); + } + } else { + pr.setTry(std::move(t)); + } + }); + return future; + } + + /// Set an error continuation for this Future where the continuation can + /// be called with a known exception type and returns a `Future` + template > + typename std::enable_if::value, Future::inner>>::type thenError(F&& fn) && { + typedef typename isFuture::inner B; + typedef std::decay_t ET; + using DF = detail::decay_t; + + Promise promise; + auto future = promise.getFuture(); + getState().setCallback([fn = std::forward(fn), pr = std::move(promise)](Try&& t) mutable { + if (t.hasException()) { + try { + std::rethrow_exception(std::move(t).exception()); + } catch (ET& e) { + try { + auto f = std::invoke(std::forward(fn), e); + std::move(f).then([pr = std::move(pr)](Try&& t) mutable { + pr.setTry(std::move(t)); + }); + } catch (...) { + pr.setException(std::current_exception()); + } + } catch (...) { + pr.setException(std::current_exception()); + } + } else { + pr.setTry(std::move(t)); + } + }); + return future; + } + + private: + explicit Future(detail::EmptyConstructor) : _state(nullptr) { + } + explicit Future(detail::SharedState* state) : _state(state) { + } + + // convenience method that checks if _state is set + inline detail::SharedState& getState() { + if (!_state) { + throw FutureException(ErrorCode::NoState); + } + return *_state; + } + inline detail::SharedState const& getState() const { + if (!_state) { + throw FutureException(ErrorCode::NoState); + } + return *_state; + } + + inline Try& getStateTryChecked() { + return getStateTryChecked(*this); + } + inline Try const& getStateTryChecked() const { + return getStateTryChecked(*this); + } + + template + static decltype(auto) getStateTryChecked(Self& self) { + auto& state = self.getState(); + if (!state.hasResult()) { + throw FutureException(ErrorCode::FutureNotReady); + } + return state.getTry(); + } + + void detach() noexcept { + detail::SharedState* state = nullptr; + std::swap(state, _state); + assert(_state == nullptr); + if (state) { + // may delete the shared state, so must be last action + state->detachFuture(); + } + } + + private: + detail::SharedState* _state; +}; + +} // namespace futures +} // namespace arangodb diff --git a/vendor/arangodb/futures/Promise-inl.h b/vendor/arangodb/futures/Promise-inl.h new file mode 100644 index 0000000..82e0041 --- /dev/null +++ b/vendor/arangodb/futures/Promise-inl.h @@ -0,0 +1,39 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2022 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Simon Grätzer +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +namespace arangodb { +namespace futures { + +template +Future Promise::getFuture() { + if (_retrieved) { + throw FutureException(ErrorCode::FutureAlreadyRetrieved); + } + _retrieved = true; + return arangodb::futures::Future(_state); +} + +} // namespace futures +} // namespace arangodb diff --git a/vendor/arangodb/futures/Promise.h b/vendor/arangodb/futures/Promise.h new file mode 100644 index 0000000..e38d1ed --- /dev/null +++ b/vendor/arangodb/futures/Promise.h @@ -0,0 +1,169 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2022 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Simon Grätzer +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include + +namespace arangodb { +namespace futures { + +template +class Future; + +/// producer side of future-promise pair +/// accesses on Promise have to be synchronized externally to +/// be thread-safe +template +class Promise { + static_assert(!std::is_same::value, "Promise instead of void"); + + public: + /// make invalid promise + static Promise makeEmpty() { + return Promise(nullptr); + } + + /// @brief Constructs a Promise with no shared state. + /// After construction, valid() == true. + Promise() : _state(detail::SharedState::make()), _retrieved(false) { + } + + Promise(Promise const& o) = delete; + Promise(Promise&& o) noexcept : _state(std::move(o._state)), _retrieved(o._retrieved) { + o._state = nullptr; + } + + ~Promise() { + this->detach(); + } + + Promise& operator=(Promise const&) = delete; + Promise& operator=(Promise&& o) noexcept { + if (this != &o) { + detach(); + _state = std::move(o._state); + _retrieved = o._retrieved; + o._retrieved = false; + o._state = nullptr; + } + return *this; + } + + bool valid() const noexcept { + return _state != nullptr; + } + + bool isFulfilled() const noexcept { + if (_state) { + return _state->hasResult(); + } + return true; + } + + /// Fulfill the Promise with an exception_ptr. + void setException(std::exception_ptr ep) { + setTry(Try(ep)); + } + + /// Fulfill the Promise with exception `e` *as if* by + /// `setException(std::make_exception_ptr(e))`. + template + typename std::enable_if::value>::type setException(E const& e) { + setException(std::make_exception_ptr(e)); + } + + /// Fulfill the Promise with the specified value using perfect forwarding. + /// Functionally equivalent to `setTry(Try(std::forward(value)))` + template + void setValue(M&& value) { + static_assert(!std::is_same::value, "Use setValue() instead"); + setTry(Try(std::forward(value))); + } + + /// set void value + template + typename std::enable_if::value>::type setValue() { + setTry(Try(std::in_place)); + } + + /// Fulfill the Promise with the specified Try (value or exception). + void setTry(Try&& t) { + throwIfFulfilled(); + getState().setResult(std::move(t)); + } + + /// Fulfill this Promise with the result of a function that takes no + /// arguments and returns something implicitly convertible to T. + template + void setWith(F&& func) { + throwIfFulfilled(); + getState().setResult(makeTryWith(std::forward(func))); + } + + arangodb::futures::Future getFuture(); + + private: + explicit Promise(detail::SharedState* state) : _state(state), _retrieved(false) { + } + + // convenience method that checks if _state is set + inline detail::SharedState& getState() { + if (!_state) { + throw FutureException(ErrorCode::NoState); + } + return *_state; + } + + inline void throwIfFulfilled() const { + if (isFulfilled()) { + throw FutureException(ErrorCode::PromiseAlreadySatisfied); + } + } + + void detach() { + if (_state) { + if (!_retrieved) { + _state->detachFuture(); + } + if (!_state->hasResult()) { + auto ptr = std::make_exception_ptr(FutureException(ErrorCode::BrokenPromise)); + _state->setResult(Try(std::move(ptr))); + } + _state->detachPromise(); + _state = nullptr; + } + } + + private: + detail::SharedState* _state; + /// Whether the Future has been retrieved (a one-time operation) + bool _retrieved; +}; +} // namespace futures +} // namespace arangodb + +#include +#include diff --git a/vendor/arangodb/futures/SharedState.h b/vendor/arangodb/futures/SharedState.h new file mode 100644 index 0000000..c2a93ce --- /dev/null +++ b/vendor/arangodb/futures/SharedState.h @@ -0,0 +1,282 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2022 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Simon Grätzer +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +#include + +namespace arangodb { +namespace futures { +namespace detail { + +/// The FSM to manage the primary producer-to-consumer info-flow has these +/// allowed (atomic) transitions: +/// +/// +-------------------------------------------------------------+ +/// | ---> OnlyResult ----- | +/// | / \ | +/// | (setResult()) (setCallback()) | +/// | / \ | +/// | Start -----> ------> Done | +/// | \ / | +/// | (setCallback()) (setResult()) | +/// | \ / | +/// | ---> OnlyCallback --- | +/// +-------------------------------------------------------------+ +template +class SharedState { + enum class State : uint8_t { + Start = 1 << 0, + OnlyResult = 1 << 1, + OnlyCallback = 1 << 2, + Done = 1 << 3, + }; + + /// Allow us to safely pass a core pointer to the Scheduler + struct SharedStateScope { + explicit SharedStateScope(SharedState* state) noexcept : _state(state) {} + SharedStateScope(SharedStateScope const&) = delete; + SharedStateScope& operator=(SharedStateScope const&) = delete; + SharedStateScope(SharedStateScope&& o) noexcept : _state(o._state) { + o._state = nullptr; + } + + ~SharedStateScope() { + if (_state) { + _state->_callback = nullptr; + _state->detachOne(); + } + } + + SharedState* _state; + }; + + public: + /// State will be Start + static SharedState* make() { return new SharedState(); } + + /// State will be OnlyResult + /// Result held will be move-constructed from `t` + static SharedState* make(Try&& t) { return new SharedState(std::move(t)); } + + /// State will be OnlyResult + /// Result held will be the `T` constructed from forwarded `args` + template + static SharedState* make(std::in_place_t, Args&&... args) { + return new SharedState(std::in_place, std::forward(args)...); + } + + // not copyable + SharedState(SharedState const&) = delete; + SharedState& operator=(SharedState const&) = delete; + + // not movable (see comment in the implementation of Future::then) + SharedState(SharedState&&) noexcept = delete; + SharedState& operator=(SharedState&&) = delete; + + /// True if state is OnlyCallback or Done. + /// May call from any thread + bool hasCallback() const noexcept { + auto const state = _state.load(std::memory_order_acquire); + return state == State::OnlyCallback || state == State::Done; + } + + /// True if state is OnlyResult or Done. + /// May call from any thread + bool hasResult() const noexcept { + auto const state = _state.load(std::memory_order_acquire); + return state == State::OnlyResult || state == State::Done; + } + + /// Call only from consumer thread (since the consumer thread can modify the + /// referenced Try object; see non-const overloads of `future.result()`, + /// etc., and certain Future-provided callbacks which move-out the result). + /// + /// Unconditionally returns a reference to the result. + /// + /// State dependent preconditions: + /// + /// - Start or OnlyCallback: Never safe - do not call. (Access in those states + /// would be undefined behavior since the producer thread can, in those + /// states, asynchronously set the referenced Try object.) + /// - OnlyResult: Always safe. (Though the consumer thread should not use the + /// returned reference after it attaches a callback unless it knows that + /// the callback does not move-out the referenced result.) + /// - Done: Safe but sometimes unusable. (Always returns a valid reference, + /// but the referenced result may or may not have been modified, including + /// possibly moved-out, depending on what the callback did; some but not + /// all callbacks modify (possibly move-out) the result.) + Try& getTry() noexcept { + assert(hasResult()); + return _result; + } + Try const& getTry() const noexcept { + assert(hasResult()); + return _result; + } + + /// Call only from consumer thread. + /// Call only once - else undefined behavior. + /// + /// See FSM graph for allowed transitions. + /// + /// If it transitions to Done, synchronously initiates a call to the callback, + /// and might also synchronously execute that callback (e.g., if there is no + /// executor or if the executor is inline). + template + void setCallback(F&& func) { + assert(!hasCallback()); + + // construct _callback first; TODO maybe try to avoid this? + _callback = std::forward(func); + + auto state = _state.load(std::memory_order_acquire); + switch (state) { + case State::Start: + if (_state.compare_exchange_strong(state, State::OnlyCallback, + std::memory_order_release)) { + return; + } + assert(state == State::OnlyResult); // race with setResult + [[fallthrough]]; + case State::OnlyResult: + // acquire is actually correct here + if (_state.compare_exchange_strong(state, State::Done, + std::memory_order_acquire)) { + doCallback(); + return; + } + [[fallthrough]]; + default: + assert(false); // unexpected state + } + } + + /// Call only from producer thread. + /// Call only once - else undefined behavior. + /// + /// See FSM graph for allowed transitions. + /// + /// If it transitions to Done, synchronously initiates a call to the callback, + /// and might also synchronously execute that callback (e.g., if there is no + /// executor or if the executor is inline). + void setResult(Try&& t) { + assert(!hasResult()); + // call move constructor of content + ::new (&_result) Try(std::move(t)); + + auto state = _state.load(std::memory_order_acquire); + + switch (state) { + case State::Start: + if (_state.compare_exchange_strong(state, State::OnlyResult, + std::memory_order_release)) { + return; + } + assert(state == State::OnlyCallback); // race with setCallback + [[fallthrough]]; + case State::OnlyCallback: + // acquire is actually correct here + if (_state.compare_exchange_strong(state, State::Done, + std::memory_order_acquire)) { + doCallback(); + return; + } + [[fallthrough]]; + + default: + assert(false); // unexpected state + } + } + + /// Called by a destructing Future (in the consumer thread, by definition). + /// Calls `delete this` if there are no more references to `this` + /// (including if `detachPromise()` is called previously or concurrently). + void detachFuture() noexcept { detachOne(); } + + /// Called by a destructing Promise (in the producer thread, by definition). + /// Calls `delete this` if there are no more references to `this` + /// (including if `detachFuture()` is called previously or concurrently). + void detachPromise() noexcept { + assert(hasResult()); + detachOne(); + } + + private: + /// empty shared state + SharedState() : _state(State::Start), _attached(2) {} + + /// use to construct a ready future + explicit SharedState(Try&& t) + : _result(std::move(t)), _state(State::OnlyResult), _attached(1) {} + + /// use to construct a ready future + template + explicit SharedState(std::in_place_t, Args&&... args) noexcept( + std::is_nothrow_constructible::value) + : _result(std::in_place, std::forward(args)...), + _state(State::OnlyResult), + _attached(1) {} + + ~SharedState() { + assert(_attached == 0); + assert(hasResult()); + _result.~Try(); + } + + /// detach promise or future from shared state + void detachOne() noexcept { + auto a = _attached.fetch_sub(1, std::memory_order_acq_rel); + assert(a >= 1); + if (a == 1) { + _callback = nullptr; + delete this; + } + } + + void doCallback() { + assert(_state == State::Done); + assert(_callback); + + _attached.fetch_add(1); + // SharedStateScope makes this exception safe + SharedStateScope scope(this); // will call detachOne() + _callback(std::move(_result)); + } + + private: + using Callback = fu2::unique_function&&)>; + Callback _callback; + union { // avoids having to construct the result + Try _result; + }; + std::atomic _state; + std::atomic _attached; +}; + +} // namespace detail +} // namespace futures +} // namespace arangodb diff --git a/vendor/arangodb/futures/Try.h b/vendor/arangodb/futures/Try.h new file mode 100644 index 0000000..bfdbe6c --- /dev/null +++ b/vendor/arangodb/futures/Try.h @@ -0,0 +1,480 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2022 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Simon Grätzer +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include + +// likely/unlikely branch indicator +// macro definitions similar to the ones at +// https://kernelnewbies.org/FAQ/LikelyUnlikely +#if defined(__GNUC__) || defined(__GNUG__) +# define ADB_LIKELY(v) __builtin_expect(!!(v), 1) +# define ADB_UNLIKELY(v) __builtin_expect(!!(v), 0) +#else +# define ADB_LIKELY(v) v +# define ADB_UNLIKELY(v) v +#endif + +namespace arangodb { +namespace futures { + +/// Try is a wrapper that contains either an instance of T, an exception, or +/// nothing. Exceptions are stored as exception_ptrs so that the user can +/// minimize rethrows if so desired. Inspired by Folly's Try +template +class Try { + static_assert(!std::is_reference::value, "Do not use with reference types"); + + enum class Content { None, Value, Exception }; + + public: + /// Value type of the Try + typedef T element_type; + + /// Construct an empty Try + Try() noexcept : _content(Content::None) { + } + + /// Construct a Try with a value by copy + /// @param v The value to copy + explicit Try(const T& v) noexcept(std::is_nothrow_copy_constructible::value) + : _value(v), _content(Content::Value) { + } + + /// Construct a Try with a value by move + /// @param v The value to move in + explicit Try(T&& v) noexcept(std::is_nothrow_move_constructible::value) + : _value(std::move(v)), _content(Content::Value) { + } + + template + explicit Try(std::in_place_t, Args&&... args) noexcept(std::is_nothrow_constructible::value) + : _value(static_cast(args)...), _content(Content::Value) { + } + + /// Construct a Try with an exception_ptr + /// @param e The exception_ptr + explicit Try(std::exception_ptr e) noexcept : _exception(std::move(e)), _content(Content::Exception) { + } + + // Move constructor + Try(Try&& t) noexcept(std::is_nothrow_move_constructible::value) : _content(t._content) { + if (_content == Content::Value) { + new (&_value) T(std::move(t._value)); + } else if (_content == Content::Exception) { + new (&_exception) std::exception_ptr(std::move(t._exception)); + } + } + + // Move assigner + Try& operator=(Try&& t) noexcept(std::is_nothrow_move_constructible::value) { + if (this == &t) { + return *this; + } + + destroy(); + if (t._content == Content::Value) { + new (&_value) T(std::move(t._value)); + } else if (t._content == Content::Exception) { + new (&_exception) std::exception_ptr(std::move(t._exception)); + } + _content = t._content; + return *this; + } + + // Copy constructor + Try(const Try& t) noexcept(std::is_nothrow_copy_constructible::value) : _content(t._content) { + static_assert(std::is_copy_constructible::value, "T must be copyable for Try to be copyable"); + if (_content == Content::Value) { + new (&_value) T(t._value); + } else if (_content == Content::Exception) { + new (&_exception) std::exception_ptr(t._exception); + } + } + // Copy assigner + Try& operator=(const Try& t) noexcept(std::is_nothrow_copy_constructible::value) { + static_assert(std::is_copy_constructible::value, "T must be copyable for Try to be copyable"); + if (this == &t) { + return *this; + } + + destroy(); + if (t._content == Content::Value) { + new (&_value) T(std::move(t._value)); + } else if (t._content == Content::Exception) { + new (&_exception) std::exception_ptr(std::move(t._exception)); + } + _content = t._content; + return *this; + } + + ~Try() { + if (ADB_LIKELY(_content == Content::Value)) { + _value.~T(); + } else if (ADB_UNLIKELY(_content == Content::Exception)) { + _exception.~exception_ptr(); + } + } + + /// In-place construct the value in the Try object. + /// Destroys any previous value prior to constructing the new value. + /// Leaves *this in an empty state if the construction of T throws. + template + T& emplace(Args&&... args) noexcept(std::is_nothrow_constructible::value) { + this->destroy(); + new (&_value) T(static_cast(args)...); + _content = Content::Value; + return _value; + } + + /// Set an exception value into this Try object. + /// Destroys any previous value prior to constructing the new value. + /// Leaves *this empty if throws + void set_exception(std::exception_ptr e) { + this->destroy(); + new (&_exception) std::exception_ptr(e); + _content = Content::Exception; + } + + /// Set an exception value into this Try object. + /// Destroys any previous value prior to constructing the new value. + /// Leaves *this empty if throws + void set_exception(std::exception_ptr&& e) { + this->destroy(); + new (&_exception) std::exception_ptr(std::move(e)); + _content = Content::Exception; + } + + template + void set_exception(E const& e) { + this->destroy(); + new (&_exception) std::exception_ptr(std::make_exception_ptr(e)); + _content = Content::Exception; + } + + /// Get a mutable reference to the contained value. If the Try contains an + /// exception it will be rethrown. + /// @return mutable reference to the contained value + T& get() & { + throwIfFailed(); + return _value; + } + /// Get a rvalue reference to the contained value. If the Try contains an + /// exception it will be rethrown. + /// @return rvalue reference to the contained value + T&& get() && { + throwIfFailed(); + return std::move(_value); + } + /// Get a const reference to the contained value. If the Try contains an + /// exception it will be rethrown. + /// @return const reference to the contained value + const T& get() const& { + throwIfFailed(); + return _value; + } + /// Get a const rvalue reference to the contained value. If the Try contains + /// an exception it will be rethrown. + /// @return const rvalue reference to the contained value + const T&& get() const&& { + throwIfFailed(); + return std::move(_value); + } + + /// If the Try contains an exception, rethrow it. Otherwise do nothing. + void throwIfFailed() const { + switch (_content) { + case Content::Value: + return; + case Content::Exception: + std::rethrow_exception(_exception); + return; + case Content::None: + default: + throw std::logic_error("Using uninitialized Try"); + } + } + + /// Const dereference operator. If the Try contains an exception it will be + /// rethrown. + /// @return const reference to the contained value + const T& operator*() const& { + return get(); + } + /* + * Dereference operator. If the Try contains an exception it will be rethrown. + * + * @return mutable reference to the contained value + */ + T& operator*() & { + return get(); + } + /* + * Mutable rvalue dereference operator. If the Try contains an exception it + * will be rethrown. + * + * @return rvalue reference to the contained value + */ + T&& operator*() && { + return std::move(get()); + } + /// Const rvalue dereference operator. If the Try contains an exception it + /// will be rethrown. + /// @return rvalue reference to the contained value + const T&& operator*() const&& { + return std::move(get()); + } + + /// Const arrow operator. If the Try contains an exception it will be + /// rethrown. + /// @return const reference to the contained value + const T* operator->() const { + return &get(); + } + + /// Arrow operator. If the Try contains an exception it will be rethrown. + /// @return mutable reference to the contained value + T* operator->() { + return &get(); + } + + /// @return True if the Try contains a value, false otherwise + bool hasValue() const { + return _content == Content::Value; + } + /// @return True if the Try contains an exception, false otherwise + bool hasException() const { + return _content == Content::Exception; + } + /// @return true if the Try contains an exception or a vlaue + bool valid() const { + return _content != Content::None; + } + + /// @throws std::logic_error if the Try doesn't contain an exception + /// @return mutable reference to the exception contained by this Try + std::exception_ptr& exception() & { + if (!hasException()) { + throw std::logic_error("Try does not contain an exception"); + } + return _exception; + } + + std::exception_ptr&& exception() && { + if (!hasException()) { + throw std::logic_error("Try does not contain an exception"); + } + return std::move(_exception); + } + + std::exception_ptr const& exception() const& { + if (!hasException()) { + throw std::logic_error("Try does not contain an exception"); + } + return _exception; + } + + std::exception_ptr const&& exception() const&& { + if (!hasException()) { + throw std::logic_error("Try does not contain an exception"); + } + return std::move(_exception); + } + + private: + void destroy() noexcept { + auto old = _content; + _content = Content::None; + if (ADB_LIKELY(old == Content::Value)) { + _value.~T(); + } else if (ADB_UNLIKELY(old == Content::Exception)) { + _exception.~exception_ptr(); + } + } + + private: + union { + T _value; + std::exception_ptr _exception; + }; + Content _content; +}; + +/// Specialization of Try for void value type. Encapsulates either success or an +/// exception. +template <> +class Try { + public: + Try() noexcept : _exception() { + assert(!hasException()); + } + explicit Try(std::exception_ptr e) : _exception(std::move(e)) { + } + Try(Try&& o) : _exception(std::move(o._exception)) { + } + + /// copy assignment + Try& operator=(const Try& e) { + _exception = e._exception; + return *this; + } + + bool hasException() const { + return _exception != nullptr; + } + bool hasValue() const { + return !hasException(); + } + + /// Try is never empty + bool valid() const { + return true; + } + + /// get the value (will throw if contains exception) + void get() const { + throwIfFailed(); + } + + /// If the Try contains an exception, throws it + inline void throwIfFailed() const { + if (hasException()) { + std::rethrow_exception(_exception); + } + } + + /// Dereference operator. If the Try contains an exception, throws it + void operator*() const { + return get(); + } + + /// @throws std::logic_error if the Try doesn't contain an exception + /// @return mutable reference to the exception contained by this Try + std::exception_ptr& exception() & { + if (!hasException()) { + throw std::logic_error("Try does not contain an exception"); + } + return _exception; + } + + std::exception_ptr&& exception() && { + if (!hasException()) { + throw std::logic_error("Try does not contain an exception"); + } + return std::move(_exception); + } + + std::exception_ptr const& exception() const& { + if (!hasException()) { + throw std::logic_error("Try does not contain an exception"); + } + return _exception; + } + + std::exception_ptr const&& exception() const&& { + if (!hasException()) { + throw std::logic_error("Try does not contain an exception"); + } + return std::move(_exception); + } + + /// In-place construct a 'void' value into this Try object. + /// This has the effect of clearing any existing exception stored + void emplace() noexcept { + _exception = nullptr; + } + + /// Set an exception value into this Try object. + /// This has the effect of clearing any existing exception stored + void set_exception(std::exception_ptr e) noexcept { + _exception = e; + } + + /// Set an exception value into this Try object. + /// This has the effect of clearing any existing exception stored + void set_exception(std::exception_ptr const& e) noexcept { + _exception = e; + } + + /// Set an exception value into this Try object. + /// This has the effect of clearing any existing exception stored + void set_exception(std::exception_ptr&& e) noexcept { + _exception = std::move(e); + } + + template + void set_exception(E const& e) { + _exception = std::make_exception_ptr(e); + } + + private: + std::exception_ptr _exception; +}; + +template ::type> +typename std::enable_if::value, Try>::type makeTryWith(F&& func) noexcept { + try { + return Try(std::in_place, func()); + } catch (...) { + return Try(std::current_exception()); + } +} + +template ::type> +typename std::enable_if::value, Try>::type makeTryWith(F&& func) noexcept { + try { + func(); + return Try(); + } catch (...) { + return Try(std::current_exception()); + } +} + +/// test for Try parameter in templates +template +struct isTry { + static constexpr bool value = false; + typedef T inner; +}; +template +struct isTry> { + static constexpr bool value = true; + typedef T inner; +}; +template +struct isTry&> { + static constexpr bool value = true; + typedef T inner; +}; +template +struct isTry&&> { + static constexpr bool value = true; + typedef T inner; +}; + +} // namespace futures +} // namespace arangodb diff --git a/vendor/arangodb/futures/Unit.h b/vendor/arangodb/futures/Unit.h new file mode 100644 index 0000000..195f49c --- /dev/null +++ b/vendor/arangodb/futures/Unit.h @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2022 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Simon Grätzer +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +namespace arangodb { +namespace futures { +/// In functional programming, the degenerate case is often called "unit". In +/// C++, "void" is often the best analogue. However, because of the syntactic +/// special-casing required for void, it is frequently a liability for template +/// metaprogramming. So, instead of writing specializations to handle cases like +/// SomeContainer, a library author may instead rule that out and simply +/// have library users use SomeContainer. Contained values may be ignored. +/// Much easier. +/// +/// "void" is the type that admits of no values at all. It is not possible to +/// construct a value of this type. +/// "unit" is the type that admits of precisely one unique value. It is +/// possible to construct a value of this type, but it is always the same value +/// every time, so it is uninteresting. +struct Unit { + constexpr bool operator==(const Unit& /*other*/) const { + return true; + } + constexpr bool operator!=(const Unit& /*other*/) const { + return false; + } +}; + +constexpr Unit unit{}; + +template +struct lift_unit { + using type = T; +}; +template <> +struct lift_unit { + using type = Unit; +}; +template +using lift_unit_t = typename lift_unit::type; + +template +struct drop_unit { + using type = T; +}; +template <> +struct drop_unit { + using type = void; +}; +template +using drop_unit_t = typename drop_unit::type; + +} // namespace futures +} // namespace arangodb diff --git a/vendor/arangodb/futures/Utilities.h b/vendor/arangodb/futures/Utilities.h new file mode 100644 index 0000000..a28e87f --- /dev/null +++ b/vendor/arangodb/futures/Utilities.h @@ -0,0 +1,273 @@ +//////////////////////////////////////////////////////////////////////////////// +/// DISCLAIMER +/// +/// Copyright 2014-2022 ArangoDB GmbH, Cologne, Germany +/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is ArangoDB GmbH, Cologne, Germany +/// +/// @author Simon Grätzer +//////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace arangodb { +namespace futures { + +template +Future makeFuture(Try&& t) { + return Future(detail::SharedState::make(std::move(t))); +} + +/// Make a complete void future +Future makeFuture(); + +/// Make a completed Future by moving in a value. e.g. +template +Future::type> makeFuture(T&& t) { + return makeFuture(Try::type>(std::forward(t))); +} + +/// Make a failed Future from an std::exception_ptr. +template +Future makeFuture(std::exception_ptr const& e) { + return makeFuture(Try(e)); +} + +/// Make a Future from an exception type E that can be passed to +/// std::make_exception_ptr(). +template +typename std::enable_if::value, Future>::type makeFuture(E const& e) { + return makeFuture(Try(std::make_exception_ptr(e))); +} + +// makeFutureWith(Future()) -> Future +template > +typename std::enable_if::value, R>::type makeFutureWith(F&& func) { + using InnerType = typename isFuture::inner; + try { + return std::forward(func)(); + } catch (...) { + return makeFuture(std::current_exception()); + } +} + +// makeFutureWith(T()) -> Future +// makeFutureWith(void()) -> Future +template > +typename std::enable_if::value, Future>::type makeFutureWith(F&& func) { + return makeFuture(makeTryWith([&func]() mutable { + return std::forward(func)(); + })); +} + +namespace detail { + +template +void _foreach(F&&, size_t) { +} + +template +void _foreach(F&& f, size_t i, Arg&& arg, Args&&... args) { + f(i, std::forward(arg)); + _foreach(i + 1, std::forward(f), std::forward(args)...); +} + +template +void foreach (F&& f, Args && ... args) { + _foreach(std::forward(f), 0, args...); +} +}; // namespace detail + +/// @brief When all the input Futures complete, the returned Future will +/// complete. Errors do not cause early termination; this Future will always +/// succeed after all its Futures have finished (whether successfully or with an +/// error). +/// The Futures are moved in, so your copies are invalid. If you need to +/// chain further from these Futures, use the variant with an output iterator. +/// This function is thread-safe for Futures running on different threads. +/// It will complete in whichever thread the last Future completes in. +/// @return for (Future, Future, ...) input is +/// Future, Try, ...>>. +// template +// Future::inner>...>> collectAll(Fs&&... +// fs) { +// using Result = std::tuple::inner>...>; +// struct Context { +// ~Context() { p.setValue(std::move(results)); } +// Promise p; +// Result results; +// }; +// auto ctx = std::make_shared(); +// +// detail::foreach ( +// [&](auto i, auto&& f) { +// f.then([i, ctx](auto&& t) { std::get(ctx->results) = std::move(t); +// }); +// }, +// std::move(fs)...); +// return ctx->p.getFuture(); +//} + +/// @brief When all the input Futures complete, the returned Future will +/// complete. Errors do not cause early termination; this Future will always +/// succeed after all its Futures have finished (whether successfully or with an +/// error). +/// The Futures are moved in, so your copies are invalid. If you need to +/// chain further from these Futures, use the variant with an output iterator. +/// This function is thread-safe for Futures running on different threads. But +/// if you are doing anything non-trivial after, you will probably want to +/// follow with `via(executor)` because it will complete in whichever thread the +/// last Future completes in. +/// The return type for Future input is a Future>> +template +Future::value_type::value_type>>> collectAll( + InputIterator first, InputIterator last) { + using FT = typename std::iterator_traits::value_type; + using T = typename FT::value_type; + + struct Context { + explicit Context(size_t n) : results(n) { + } + ~Context() { + p.setValue(std::move(results)); + } + Promise>> p; + std::vector> results; + }; + + auto ctx = std::make_shared(size_t(std::distance(first, last))); + for (size_t i = 0; first != last; ++first, ++i) { + std::move(*first).thenFinal([i, ctx](auto&& t) { + ctx->results[i] = std::move(t); + }); + } + + return ctx->p.getFuture(); +} + +template +auto collectAll(Collection&& c) -> decltype(collectAll(std::begin(c), std::end(c))) { + return collectAll(std::begin(c), std::end(c)); +} + +namespace detail { +namespace gather { + +template +void thenFinalAll(C& c, std::index_sequence, Future&&... ts) { + (std::move(ts).thenFinal([&](Try&& t) { + std::get(c->results) = std::move(t); + }), + ...); +} +} // namespace gather +} // namespace detail + +// like collectAll but uses a tuple instead and works with different types +// returns a Future>...> +template +auto gather(Future&&... r) { + using try_tuple = std::tuple...>; + + struct Context { + ~Context() { + p.setValue(std::move(results)); + } + try_tuple results; + Promise p; + }; + + auto ctx = std::make_shared(); + detail::gather::thenFinalAll(ctx, std::index_sequence_for{}, std::forward>(r)...); + return ctx->p.getFuture(); +}; + +template +T gather(T t) { + return t; +}; + +namespace detail { +namespace collect { + +template +void thenFinalAll(C& c, std::index_sequence, Future&&... ts) { + (std::move(ts).thenFinal([c](Try&& t) { + if (t.hasException()) { + if (c->hadError.exchange(true, std::memory_order_release) == false) { + c->p.setException(std::move(t).exception()); + } + } else { + std::get(c->results) = std::move(t); + } + }), + ...); +} + +template +auto unpackAll(std::tuple...>& c, std::index_sequence) -> std::tuple { + return std::make_tuple(std::move(std::get(c)).get()...); +} + +template +auto unpackAll(std::tuple...>& c) -> std::tuple { + return unpackAll(c, std::index_sequence_for{}); +} + +} // namespace collect +} // namespace detail + +// like collectAll but uses a tuple instead and works with different types +// returns a Future>...> +template +auto collect(Future&&... r) { + using try_tuple = std::tuple...>; + using value_tuple = std::tuple; + + struct Context { + Context() : hadError(false) { + } + ~Context() { + if (hadError.load(std::memory_order_acquire) == false) { + p.setValue(detail::collect::unpackAll(results)); + } + } + try_tuple results; + Promise p; + std::atomic hadError; + }; + + auto ctx = std::make_shared(); + detail::collect::thenFinalAll(ctx, std::index_sequence_for{}, std::forward>(r)...); + return ctx->p.getFuture(); +} + +template +T collect(T t) { + return t; +} + +} // namespace futures +} // namespace arangodb diff --git a/vendor/arangodb/futures/function2.hpp b/vendor/arangodb/futures/function2.hpp new file mode 100644 index 0000000..f70910b --- /dev/null +++ b/vendor/arangodb/futures/function2.hpp @@ -0,0 +1,1829 @@ + +// Copyright 2015-2020 Denis Blank +// Distributed under the Boost Software License, Version 1.0 +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef FU2_INCLUDED_FUNCTION2_HPP_ +#define FU2_INCLUDED_FUNCTION2_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +// Defines: +// - FU2_HAS_DISABLED_EXCEPTIONS +#if defined(FU2_WITH_DISABLED_EXCEPTIONS) || \ + defined(FU2_MACRO_DISABLE_EXCEPTIONS) +#define FU2_HAS_DISABLED_EXCEPTIONS +#else // FU2_WITH_DISABLED_EXCEPTIONS +#if defined(_MSC_VER) +#if !defined(_HAS_EXCEPTIONS) || (_HAS_EXCEPTIONS == 0) +#define FU2_HAS_DISABLED_EXCEPTIONS +#endif +#elif defined(__clang__) +#if !(__EXCEPTIONS && __has_feature(cxx_exceptions)) +#define FU2_HAS_DISABLED_EXCEPTIONS +#endif +#elif defined(__GNUC__) +#if !__EXCEPTIONS +#define FU2_HAS_DISABLED_EXCEPTIONS +#endif +#endif +#endif // FU2_WITH_DISABLED_EXCEPTIONS +// - FU2_HAS_LIMITED_EMPTY_PROPAGATION +#if defined(FU2_WITH_LIMITED_EMPTY_PROPAGATION) +#define FU2_HAS_LIMITED_EMPTY_PROPAGATION +#endif // FU2_WITH_NO_EMPTY_PROPAGATION +// - FU2_HAS_NO_FUNCTIONAL_HEADER +#if !defined(FU2_WITH_NO_FUNCTIONAL_HEADER) && \ + !defined(FU2_NO_FUNCTIONAL_HEADER) && \ + (!defined(FU2_HAS_DISABLED_EXCEPTIONS) || \ + defined(FU2_HAS_LIMITED_EMPTY_PROPAGATION)) +#include +#else +#define FU2_HAS_NO_FUNCTIONAL_HEADER +#endif +// - FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE +#if defined(FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE) +#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE +#else // FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE +#if defined(_MSC_VER) +#if defined(_HAS_CXX17) && _HAS_CXX17 +#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE +#endif +#elif defined(__cpp_noexcept_function_type) +#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE +#elif defined(__cplusplus) && (__cplusplus >= 201703L) +#define FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE +#endif +#endif // FU2_WITH_CXX17_NOEXCEPT_FUNCTION_TYPE + +// - FU2_HAS_NO_EMPTY_PROPAGATION +#if defined(FU2_WITH_NO_EMPTY_PROPAGATION) +#define FU2_HAS_NO_EMPTY_PROPAGATION +#endif // FU2_WITH_NO_EMPTY_PROPAGATION + +#if !defined(FU2_HAS_DISABLED_EXCEPTIONS) +#include +#endif + +#if defined(__cpp_constexpr) && (__cpp_constexpr >= 201304) +#define FU2_DETAIL_CXX14_CONSTEXPR constexpr +#elif defined(__clang__) && defined(__has_feature) +#if __has_feature(__cxx_generic_lambdas__) && \ + __has_feature(__cxx_relaxed_constexpr__) +#define FU2_DETAIL_CXX14_CONSTEXPR constexpr +#endif +#elif defined(_MSC_VER) && (_MSC_VER >= 1915) && (_MSVC_LANG >= 201402) +#define FU2_DETAIL_CXX14_CONSTEXPR constexpr +#endif +#ifndef FU2_DETAIL_CXX14_CONSTEXPR +#define FU2_DETAIL_CXX14_CONSTEXPR +#endif + +/// Hint for the compiler that this point should be unreachable +#if defined(_MSC_VER) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __assume(false) +#elif defined(__GNUC__) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable() +#elif defined(__has_builtin) +#if __has_builtin(__builtin_unreachable) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE_INTRINSIC() __builtin_unreachable() +#endif +#endif +#ifndef FU2_DETAIL_UNREACHABLE_INTRINSIC +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE_INTRINSIC() abort() +#endif + +/// Causes the application to exit abnormally +#if defined(_MSC_VER) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_TRAP() __debugbreak() +#elif defined(__GNUC__) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_TRAP() __builtin_trap() +#elif defined(__has_builtin) +#if __has_builtin(__builtin_trap) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_TRAP() __builtin_trap() +#endif +#endif +#ifndef FU2_DETAIL_TRAP +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_TRAP() *(volatile int*)0x11 = 0 +#endif + +#ifndef NDEBUG +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE() ::fu2::detail::unreachable_debug() +#else +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define FU2_DETAIL_UNREACHABLE() FU2_DETAIL_UNREACHABLE_INTRINSIC() +#endif + +namespace fu2 { +inline namespace abi_400 { +namespace detail { +template +class function; + +template +struct identity {}; + +// Equivalent to C++17's std::void_t which targets a bug in GCC, +// that prevents correct SFINAE behavior. +// See http://stackoverflow.com/questions/35753920 for details. +template +struct deduce_to_void : std::common_type {}; + +template +using void_t = typename deduce_to_void::type; + +template +using unrefcv_t = std::remove_cv_t>; + +template +struct lazy_and; + +template +struct lazy_and : B1 {}; + +template +struct lazy_and : std::conditional::type {}; + +// template +// struct lazy_and +// : std::conditional, B1>::type {}; + +// Copy enabler helper class +template +struct copyable {}; +template <> +struct copyable { + copyable() = default; + ~copyable() = default; + copyable(copyable const&) = delete; + copyable(copyable&&) = default; + copyable& operator=(copyable const&) = delete; + copyable& operator=(copyable&&) = default; +}; + +/// Configuration trait to configure the function_base class. +template +struct config { + // Is true if the function is owning. + static constexpr auto const is_owning = Owning; + + // Is true if the function is copyable. + static constexpr auto const is_copyable = Copyable; + + // The internal capacity of the function + // used in small functor optimization. + // The object shall expose the real capacity through Capacity::capacity + // and the intended alignment through Capacity::alignment. + using capacity = Capacity; +}; + +/// A config which isn't compatible to other configs +template +struct property { + // Is true when the function throws an exception on empty invocation. + static constexpr auto const is_throwing = Throws; + + // Is true when the function throws an exception on empty invocation. + static constexpr auto const is_strong_exception_guaranteed = + HasStrongExceptGuarantee; +}; + +#ifndef NDEBUG +[[noreturn]] inline void unreachable_debug() { + FU2_DETAIL_TRAP(); + std::abort(); +} +#endif + +/// Provides utilities for invocing callable objects +namespace invocation { +/// Invokes the given callable object with the given arguments +template +constexpr auto invoke(Callable&& callable, Args&&... args) noexcept( + noexcept(std::forward(callable)(std::forward(args)...))) + -> decltype(std::forward(callable)(std::forward(args)...)) { + + return std::forward(callable)(std::forward(args)...); +} +/// Invokes the given member function pointer by reference +template +constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept( + noexcept((std::forward(self).*member)(std::forward(args)...))) + -> decltype((std::forward(self).* + member)(std::forward(args)...)) { + return (std::forward(self).*member)(std::forward(args)...); +} +/// Invokes the given member function pointer by pointer +template +constexpr auto invoke(Type T::*member, Self&& self, Args&&... args) noexcept( + noexcept((std::forward(self)->*member)(std::forward(args)...))) + -> decltype((std::forward(self)->*member)( + std::forward(args)...)) { + return (std::forward(self)->*member)(std::forward(args)...); +} +/// Invokes the given pointer to a scalar member by reference +template +constexpr auto +invoke(Type T::*member, + Self&& self) noexcept(noexcept(std::forward(self).*member)) + -> decltype(std::forward(self).*member) { + return (std::forward(self).*member); +} +/// Invokes the given pointer to a scalar member by pointer +template +constexpr auto +invoke(Type T::*member, + Self&& self) noexcept(noexcept(std::forward(self)->*member)) + -> decltype(std::forward(self)->*member) { + return std::forward(self)->*member; +} + +/// Deduces to a true type if the callable object can be invoked with +/// the given arguments. +/// We don't use invoke here because MSVC can't evaluate the nested expression +/// SFINAE here. +template +struct can_invoke : std::false_type {}; +template +struct can_invoke, + decltype((void)std::declval()(std::declval()...))> + : std::true_type {}; +template +struct can_invoke, + decltype((void)((std::declval().*std::declval())( + std::declval()...)))> : std::true_type {}; +template +struct can_invoke, + decltype(( + void)((std::declval().*std::declval())( + std::declval()...)))> : std::true_type {}; +template +struct can_invoke, + decltype(( + void)((std::declval()->*std::declval())( + std::declval()...)))> : std::true_type {}; +template +struct can_invoke, + decltype((void)(std::declval().*std::declval()))> + : std::true_type {}; +template +struct can_invoke, + decltype((void)(std::declval().* + std::declval()))> : std::true_type { +}; +template +struct can_invoke, + decltype(( + void)(std::declval()->*std::declval()))> + : std::true_type {}; + +template +struct is_noexcept_correct : std::true_type {}; +template +struct is_noexcept_correct> + : std::integral_constant(), std::declval()...))> { +}; +} // end namespace invocation + +namespace overloading { +template +struct overload_impl; +template +struct overload_impl : Current, + overload_impl { + explicit overload_impl(Current current, Next next, Rest... rest) + : Current(std::move(current)), overload_impl( + std::move(next), std::move(rest)...) { + } + + using Current::operator(); + using overload_impl::operator(); +}; +template +struct overload_impl : Current { + explicit overload_impl(Current current) : Current(std::move(current)) { + } + + using Current::operator(); +}; + +template +constexpr auto overload(T&&... callables) { + return overload_impl...>{std::forward(callables)...}; +} +} // namespace overloading + +/// Declares the namespace which provides the functionality to work with a +/// type-erased object. +namespace type_erasure { +/// Specialization to work with addresses of callable objects +template +struct address_taker { + template + static void* take(O&& obj) { + return std::addressof(obj); + } + static T& restore(void* ptr) { + return *static_cast(ptr); + } + static T const& restore(void const* ptr) { + return *static_cast(ptr); + } + static T volatile& restore(void volatile* ptr) { + return *static_cast(ptr); + } + static T const volatile& restore(void const volatile* ptr) { + return *static_cast(ptr); + } +}; +/// Specialization to work with addresses of raw function pointers +template +struct address_taker::value>> { + template + static void* take(O&& obj) { + return reinterpret_cast(obj); + } + template + static T restore(O ptr) { + return reinterpret_cast(const_cast(ptr)); + } +}; + +template +struct box_factory; +/// Store the allocator inside the box +template +struct box : private Allocator { + friend box_factory; + + T value_; + + explicit box(T value, Allocator allocator_) + : Allocator(std::move(allocator_)), value_(std::move(value)) { + } + + box(box&&) = default; + box(box const&) = default; + box& operator=(box&&) = default; + box& operator=(box const&) = default; + ~box() = default; +}; +template +struct box : private Allocator { + friend box_factory; + + T value_; + + explicit box(T value, Allocator allocator_) + : Allocator(std::move(allocator_)), value_(std::move(value)) { + } + + box(box&&) = default; + box(box const&) = delete; + box& operator=(box&&) = default; + box& operator=(box const&) = delete; + ~box() = default; +}; + +template +struct box_factory> { + using real_allocator = + typename std::allocator_traits>:: + template rebind_alloc>; + + /// Allocates space through the boxed allocator + static box* + box_allocate(box const* me) { + real_allocator allocator_(*static_cast(me)); + + return static_cast*>( + std::allocator_traits::allocate(allocator_, 1U)); + } + + /// Destroys the box through the given allocator + static void box_deallocate(box* me) { + real_allocator allocator_(*static_cast(me)); + + me->~box(); + std::allocator_traits::deallocate(allocator_, me, 1U); + } +}; + +/// Creates a box containing the given value and allocator +template +auto make_box(std::integral_constant, T&& value, + Allocator&& allocator_) { + return box, std::decay_t>( + std::forward(value), std::forward(allocator_)); +} + +template +struct is_box : std::false_type {}; +template +struct is_box> : std::true_type {}; + +/// Provides access to the pointer to a heal allocated erased object +/// as well to the inplace storage. +union data_accessor { + data_accessor() = default; + explicit constexpr data_accessor(std::nullptr_t) noexcept : ptr_(nullptr) { + } + explicit constexpr data_accessor(void* ptr) noexcept : ptr_(ptr) { + } + + /// The pointer we use if the object is on the heap + void* ptr_; + /// The first field of the inplace storage + std::size_t inplace_storage_; +}; + +/// See opcode::op_fetch_empty +static FU2_DETAIL_CXX14_CONSTEXPR void write_empty(data_accessor* accessor, + bool empty) noexcept { + accessor->inplace_storage_ = std::size_t(empty); +} + +template +using transfer_const_t = + std::conditional_t>::value, + std::add_const_t, To>; +template +using transfer_volatile_t = + std::conditional_t>::value, + std::add_volatile_t, To>; + +/// The retriever when the object is allocated inplace +template +FU2_DETAIL_CXX14_CONSTEXPR auto retrieve(std::true_type /*is_inplace*/, + Accessor from, + std::size_t from_capacity) { + using type = transfer_const_t>*; + + /// Process the command by using the data inside the internal capacity + auto storage = &(from->inplace_storage_); + auto inplace = const_cast(static_cast(storage)); + return type(std::align(alignof(T), sizeof(T), inplace, from_capacity)); +} + +/// The retriever which is used when the object is allocated +/// through the allocator +template +constexpr auto retrieve(std::false_type /*is_inplace*/, Accessor from, + std::size_t /*from_capacity*/) { + + return from->ptr_; +} + +namespace invocation_table { +#if !defined(FU2_HAS_DISABLED_EXCEPTIONS) +#if defined(FU2_HAS_NO_FUNCTIONAL_HEADER) +struct bad_function_call : std::exception { + bad_function_call() noexcept { + } + + char const* what() const noexcept override { + return "bad function call"; + } +}; +#else +using std::bad_function_call; +#endif +#endif + +#ifdef FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE +#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) \ + F(, , noexcept, , &) \ + F(const, , noexcept, , &) \ + F(, volatile, noexcept, , &) \ + F(const, volatile, noexcept, , &) \ + F(, , noexcept, &, &) \ + F(const, , noexcept, &, &) \ + F(, volatile, noexcept, &, &) \ + F(const, volatile, noexcept, &, &) \ + F(, , noexcept, &&, &&) \ + F(const, , noexcept, &&, &&) \ + F(, volatile, noexcept, &&, &&) \ + F(const, volatile, noexcept, &&, &&) +#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) \ + F(, , noexcept) \ + F(const, , noexcept) \ + F(, volatile, noexcept) \ + F(const, volatile, noexcept) +#else // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE +#define FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) +#define FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) +#endif // FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE + +#define FU2_DETAIL_EXPAND_QUALIFIERS(F) \ + F(, , , , &) \ + F(const, , , , &) \ + F(, volatile, , , &) \ + F(const, volatile, , , &) \ + F(, , , &, &) \ + F(const, , , &, &) \ + F(, volatile, , &, &) \ + F(const, volatile, , &, &) \ + F(, , , &&, &&) \ + F(const, , , &&, &&) \ + F(, volatile, , &&, &&) \ + F(const, volatile, , &&, &&) \ + FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT(F) +#define FU2_DETAIL_EXPAND_CV(F) \ + F(, , ) \ + F(const, , ) \ + F(, volatile, ) \ + F(const, volatile, ) \ + FU2_DETAIL_EXPAND_CV_NOEXCEPT(F) + +/// If the function is qualified as noexcept, the call will never throw +template +[[noreturn]] void throw_or_abortnoexcept( + std::integral_constant /*is_throwing*/) noexcept { + std::abort(); +} +/// Calls std::abort on empty function calls +[[noreturn]] inline void +throw_or_abort(std::false_type /*is_throwing*/) noexcept { + std::abort(); +} +/// Throws bad_function_call on empty funciton calls +[[noreturn]] inline void throw_or_abort(std::true_type /*is_throwing*/) { +#ifdef FU2_HAS_DISABLED_EXCEPTIONS + throw_or_abort(std::false_type{}); +#else + throw bad_function_call{}; +#endif +} + +template +struct function_trait; + +using is_noexcept_ = std::false_type; +using is_noexcept_noexcept = std::true_type; + +#define FU2_DEFINE_FUNCTION_TRAIT(CONST, VOLATILE, NOEXCEPT, OVL_REF, REF) \ + template \ + struct function_trait { \ + using pointer_type = Ret (*)(data_accessor CONST VOLATILE*, \ + std::size_t capacity, Args...); \ + template \ + struct internal_invoker { \ + static Ret invoke(data_accessor CONST VOLATILE* data, \ + std::size_t capacity, Args... args) NOEXCEPT { \ + auto obj = retrieve(std::integral_constant{}, \ + data, capacity); \ + auto box = static_cast(obj); \ + return invocation::invoke( \ + static_castvalue_)> CONST VOLATILE \ + REF>(box->value_), \ + std::forward(args)...); \ + } \ + }; \ + \ + template \ + struct view_invoker { \ + static Ret invoke(data_accessor CONST VOLATILE* data, std::size_t, \ + Args... args) NOEXCEPT { \ + \ + auto ptr = static_cast(data->ptr_); \ + return invocation::invoke(address_taker::restore(ptr), \ + std::forward(args)...); \ + } \ + }; \ + \ + template \ + using callable = T CONST VOLATILE REF; \ + \ + using arguments = identity; \ + \ + using is_noexcept = is_noexcept_##NOEXCEPT; \ + \ + template \ + struct empty_invoker { \ + static Ret invoke(data_accessor CONST VOLATILE* /*data*/, \ + std::size_t /*capacity*/, Args... /*args*/) NOEXCEPT { \ + throw_or_abort##NOEXCEPT(std::integral_constant{}); \ + } \ + }; \ + }; + +FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT) +#undef FU2_DEFINE_FUNCTION_TRAIT + +/// Deduces to the function pointer to the given signature +template +using function_pointer_of = typename function_trait::pointer_type; + +template +struct invoke_table; + +/// We optimize the vtable_t in case there is a single function overload +template +struct invoke_table { + using type = function_pointer_of; + + /// Return the function pointer itself + template + static constexpr auto fetch(type pointer) noexcept { + static_assert(Index == 0U, "The index should be 0 here!"); + return pointer; + } + + /// Returns the thunk of an single overloaded callable + template + static constexpr type get_invocation_table_of() noexcept { + return &function_trait::template internal_invoker::invoke; + } + /// Returns the thunk of an single overloaded callable + template + static constexpr type get_invocation_view_table_of() noexcept { + return &function_trait::template view_invoker::invoke; + } + /// Returns the thunk of an empty single overloaded callable + template + static constexpr type get_empty_invocation_table() noexcept { + return &function_trait::template empty_invoker::invoke; + } +}; +/// We generate a table in case of multiple function overloads +template +struct invoke_table { + using type = + std::tuple, function_pointer_of, + function_pointer_of...> const*; + + /// Return the function pointer at the particular index + template + static constexpr auto fetch(type table) noexcept { + return std::get(*table); + } + + /// The invocation vtable for a present object + template + struct invocation_vtable : public std::tuple, + function_pointer_of, + function_pointer_of...> { + constexpr invocation_vtable() noexcept + : std::tuple, function_pointer_of, + function_pointer_of...>(std::make_tuple( + &function_trait::template internal_invoker< + T, IsInplace>::invoke, + &function_trait::template internal_invoker< + T, IsInplace>::invoke, + &function_trait::template internal_invoker< + T, IsInplace>::invoke...)) { + } + }; + + /// Returns the thunk of an multi overloaded callable + template + static type get_invocation_table_of() noexcept { + static invocation_vtable const table; + return &table; + } + + /// The invocation vtable for a present object + template + struct invocation_view_vtable + : public std::tuple, + function_pointer_of, + function_pointer_of...> { + constexpr invocation_view_vtable() noexcept + : std::tuple, function_pointer_of, + function_pointer_of...>(std::make_tuple( + &function_trait::template view_invoker::invoke, + &function_trait::template view_invoker::invoke, + &function_trait::template view_invoker::invoke...)) { + } + }; + + /// Returns the thunk of an multi overloaded callable + template + static type get_invocation_view_table_of() noexcept { + static invocation_view_vtable const table; + return &table; + } + + /// The invocation table for an empty wrapper + template + struct empty_vtable : public std::tuple, + function_pointer_of, + function_pointer_of...> { + constexpr empty_vtable() noexcept + : std::tuple, function_pointer_of, + function_pointer_of...>( + std::make_tuple(&function_trait::template empty_invoker< + IsThrowing>::invoke, + &function_trait::template empty_invoker< + IsThrowing>::invoke, + &function_trait::template empty_invoker< + IsThrowing>::invoke...)) { + } + }; + + /// Returns the thunk of an multi single overloaded callable + template + static type get_empty_invocation_table() noexcept { + static empty_vtable const table; + return &table; + } +}; + +template +class operator_impl; + +#define FU2_DEFINE_FUNCTION_TRAIT(CONST, VOLATILE, NOEXCEPT, OVL_REF, REF) \ + template \ + class operator_impl \ + : operator_impl { \ + \ + template \ + friend class operator_impl; \ + \ + protected: \ + operator_impl() = default; \ + ~operator_impl() = default; \ + operator_impl(operator_impl const&) = default; \ + operator_impl(operator_impl&&) = default; \ + operator_impl& operator=(operator_impl const&) = default; \ + operator_impl& operator=(operator_impl&&) = default; \ + \ + using operator_impl::operator(); \ + \ + Ret operator()(Args... args) CONST VOLATILE OVL_REF NOEXCEPT { \ + auto parent = static_cast(this); \ + using erasure_t = std::decay_terasure_)>; \ + \ + /* `std::decay_terasure_)>` is a workaround for a */ \ + /* compiler regression of MSVC 16.3.1, see #29 for details. */ \ + return std::decay_terasure_)>::template invoke( \ + static_cast(parent->erasure_), \ + std::forward(args)...); \ + } \ + }; \ + template \ + class operator_impl, \ + Ret(Args...) CONST VOLATILE OVL_REF NOEXCEPT> \ + : copyable { \ + \ + template \ + friend class operator_impl; \ + \ + protected: \ + operator_impl() = default; \ + ~operator_impl() = default; \ + operator_impl(operator_impl const&) = default; \ + operator_impl(operator_impl&&) = default; \ + operator_impl& operator=(operator_impl const&) = default; \ + operator_impl& operator=(operator_impl&&) = default; \ + \ + Ret operator()(Args... args) CONST VOLATILE OVL_REF NOEXCEPT { \ + auto parent = \ + static_cast CONST VOLATILE*>(this); \ + using erasure_t = std::decay_terasure_)>; \ + \ + /* `std::decay_terasure_)>` is a workaround for a */ \ + /* compiler regression of MSVC 16.3.1, see #29 for details. */ \ + return std::decay_terasure_)>::template invoke( \ + static_cast(parent->erasure_), \ + std::forward(args)...); \ + } \ + }; + +FU2_DETAIL_EXPAND_QUALIFIERS(FU2_DEFINE_FUNCTION_TRAIT) +#undef FU2_DEFINE_FUNCTION_TRAIT +} // namespace invocation_table + +namespace tables { +/// Identifies the action which is dispatched on the erased object +enum class opcode { + op_move, ///< Move the object and set the vtable + op_copy, ///< Copy the object and set the vtable + op_destroy, ///< Destroy the object and reset the vtable + op_weak_destroy, ///< Destroy the object without resetting the vtable + op_fetch_empty, ///< Stores true or false into the to storage + ///< to indicate emptiness +}; + +/// Abstraction for a vtable together with a command table +/// TODO Add optimization for a single formal argument +/// TODO Add optimization to merge both tables if the function is size +/// optimized +template +class vtable; +template +class vtable> { + using command_function_t = void (*)(vtable* /*this*/, opcode /*op*/, + data_accessor* /*from*/, + std::size_t /*from_capacity*/, + data_accessor* /*to*/, + std::size_t /*to_capacity*/); + + using invoke_table_t = invocation_table::invoke_table; + + command_function_t cmd_; + typename invoke_table_t::type vtable_; + + template + struct trait { + static_assert(is_box::value, + "The trait must be specialized with a box!"); + + /// The command table + template + static void process_cmd(vtable* to_table, opcode op, data_accessor* from, + std::size_t from_capacity, data_accessor* to, + std::size_t to_capacity) { + + switch (op) { + case opcode::op_move: { + /// Retrieve the pointer to the object + auto box = static_cast(retrieve( + std::integral_constant{}, from, from_capacity)); + assert(box && "The object must not be over aligned or null!"); + + if (!IsInplace) { + // Just swap both pointers if we allocated on the heap + to->ptr_ = from->ptr_; + +#ifndef NDEBUG + // We don't need to null the pointer since we know that + // we don't own the data anymore through the vtable + // which is set to empty. + from->ptr_ = nullptr; +#endif + + to_table->template set_allocated(); + + } + // The object is allocated inplace + else { + construct(std::true_type{}, std::move(*box), to_table, to, + to_capacity); + box->~T(); + } + return; + } + case opcode::op_copy: { + auto box = static_cast(retrieve( + std::integral_constant{}, from, from_capacity)); + assert(box && "The object must not be over aligned or null!"); + + assert(std::is_copy_constructible::value && + "The box is required to be copyable here!"); + + // Try to allocate the object inplace + construct(std::is_copy_constructible{}, *box, to_table, to, + to_capacity); + return; + } + case opcode::op_destroy: + case opcode::op_weak_destroy: { + + assert(!to && !to_capacity && "Arg overflow!"); + auto box = static_cast(retrieve( + std::integral_constant{}, from, from_capacity)); + + if (IsInplace) { + box->~T(); + } else { + box_factory::box_deallocate(box); + } + + if (op == opcode::op_destroy) { + to_table->set_empty(); + } + return; + } + case opcode::op_fetch_empty: { + write_empty(to, false); + return; + } + } + + FU2_DETAIL_UNREACHABLE(); + } + + template + static void + construct(std::true_type /*apply*/, Box&& box, vtable* to_table, + data_accessor* to, + std::size_t to_capacity) noexcept(HasStrongExceptGuarantee) { + // Try to allocate the object inplace + void* storage = retrieve(std::true_type{}, to, to_capacity); + if (storage) { + to_table->template set_inplace(); + } else { + // Allocate the object through the allocator + to->ptr_ = storage = + box_factory>::box_allocate(std::addressof(box)); + to_table->template set_allocated(); + } + new (storage) T(std::forward(box)); + } + + template + static void + construct(std::false_type /*apply*/, Box&& /*box*/, vtable* /*to_table*/, + data_accessor* /*to*/, + std::size_t /*to_capacity*/) noexcept(HasStrongExceptGuarantee) { + } + }; + + /// The command table + static void empty_cmd(vtable* to_table, opcode op, data_accessor* /*from*/, + std::size_t /*from_capacity*/, data_accessor* to, + std::size_t /*to_capacity*/) { + + switch (op) { + case opcode::op_move: + case opcode::op_copy: { + to_table->set_empty(); + break; + } + case opcode::op_destroy: + case opcode::op_weak_destroy: { + // Do nothing + break; + } + case opcode::op_fetch_empty: { + write_empty(to, true); + break; + } + default: { + FU2_DETAIL_UNREACHABLE(); + } + } + } + +public: + vtable() noexcept = default; + + /// Initialize an object at the given position + template + static void init(vtable& table, T&& object, data_accessor* to, + std::size_t to_capacity) { + + trait>::construct(std::true_type{}, std::forward(object), + &table, to, to_capacity); + } + + /// Moves the object at the given position + void move(vtable& to_table, data_accessor* from, std::size_t from_capacity, + data_accessor* to, + std::size_t to_capacity) noexcept(HasStrongExceptGuarantee) { + cmd_(&to_table, opcode::op_move, from, from_capacity, to, to_capacity); + set_empty(); + } + + /// Destroys the object at the given position + void copy(vtable& to_table, data_accessor const* from, + std::size_t from_capacity, data_accessor* to, + std::size_t to_capacity) const { + cmd_(&to_table, opcode::op_copy, const_cast(from), + from_capacity, to, to_capacity); + } + + /// Destroys the object at the given position + void destroy(data_accessor* from, + std::size_t from_capacity) noexcept(HasStrongExceptGuarantee) { + cmd_(this, opcode::op_destroy, from, from_capacity, nullptr, 0U); + } + + /// Destroys the object at the given position without invalidating the + /// vtable + void + weak_destroy(data_accessor* from, + std::size_t from_capacity) noexcept(HasStrongExceptGuarantee) { + cmd_(this, opcode::op_weak_destroy, from, from_capacity, nullptr, 0U); + } + + /// Returns true when the vtable doesn't hold any erased object + bool empty() const noexcept { + data_accessor data; + cmd_(nullptr, opcode::op_fetch_empty, nullptr, 0U, &data, 0U); + return bool(data.inplace_storage_); + } + + /// Invoke the function at the given index + template + constexpr decltype(auto) invoke(Args&&... args) const { + auto thunk = invoke_table_t::template fetch(vtable_); + return thunk(std::forward(args)...); + } + /// Invoke the function at the given index + template + constexpr decltype(auto) invoke(Args&&... args) const volatile { + auto thunk = invoke_table_t::template fetch(vtable_); + return thunk(std::forward(args)...); + } + + template + void set_inplace() noexcept { + using type = std::decay_t; + vtable_ = invoke_table_t::template get_invocation_table_of(); + cmd_ = &trait::template process_cmd; + } + + template + void set_allocated() noexcept { + using type = std::decay_t; + vtable_ = invoke_table_t::template get_invocation_table_of(); + cmd_ = &trait::template process_cmd; + } + + void set_empty() noexcept { + vtable_ = invoke_table_t::template get_empty_invocation_table(); + cmd_ = &empty_cmd; + } +}; +} // namespace tables + +/// A union which makes the pointer to the heap object share the +/// same space with the internal capacity. +/// The storage type is distinguished by multiple versions of the +/// control and vtable. +template +struct internal_capacity { + /// We extend the union through a technique similar to the tail object hack + typedef union { + /// Tag to access the structure in a type-safe way + data_accessor accessor_; + /// The internal capacity we use to allocate in-place + std::aligned_storage_t capacity_; + } type; +}; +template +struct internal_capacity< + Capacity, std::enable_if_t<(Capacity::capacity < sizeof(void*))>> { + typedef struct { + /// Tag to access the structure in a type-safe way + data_accessor accessor_; + } type; +}; + +template +class internal_capacity_holder { + // Tag to access the structure in a type-safe way + typename internal_capacity::type storage_; + +public: + constexpr internal_capacity_holder() = default; + + FU2_DETAIL_CXX14_CONSTEXPR data_accessor* opaque_ptr() noexcept { + return &storage_.accessor_; + } + constexpr data_accessor const* opaque_ptr() const noexcept { + return &storage_.accessor_; + } + FU2_DETAIL_CXX14_CONSTEXPR data_accessor volatile* + opaque_ptr() volatile noexcept { + return &storage_.accessor_; + } + constexpr data_accessor const volatile* opaque_ptr() const volatile noexcept { + return &storage_.accessor_; + } + + static constexpr std::size_t capacity() noexcept { + return sizeof(storage_); + } +}; + +/// An owning erasure +template +class erasure : internal_capacity_holder { + template + friend class erasure; + template + friend class operator_impl; + + using vtable_t = tables::vtable; + + vtable_t vtable_; + +public: + /// Returns the capacity of this erasure + static constexpr std::size_t capacity() noexcept { + return internal_capacity_holder::capacity(); + } + + FU2_DETAIL_CXX14_CONSTEXPR erasure() noexcept { + vtable_.set_empty(); + } + + FU2_DETAIL_CXX14_CONSTEXPR erasure(std::nullptr_t) noexcept { + vtable_.set_empty(); + } + + FU2_DETAIL_CXX14_CONSTEXPR + erasure(erasure&& right) noexcept(Property::is_strong_exception_guaranteed) { + right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(), + this->opaque_ptr(), capacity()); + } + + FU2_DETAIL_CXX14_CONSTEXPR erasure(erasure const& right) { + right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(), + this->opaque_ptr(), capacity()); + } + + template + FU2_DETAIL_CXX14_CONSTEXPR + erasure(erasure right) noexcept( + Property::is_strong_exception_guaranteed) { + right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(), + this->opaque_ptr(), capacity()); + } + + template >> + FU2_DETAIL_CXX14_CONSTEXPR erasure(std::false_type /*use_bool_op*/, + T&& callable, + Allocator&& allocator_ = Allocator{}) { + vtable_t::init(vtable_, + type_erasure::make_box( + std::integral_constant{}, + std::forward(callable), + std::forward(allocator_)), + this->opaque_ptr(), capacity()); + } + template >> + FU2_DETAIL_CXX14_CONSTEXPR erasure(std::true_type /*use_bool_op*/, + T&& callable, + Allocator&& allocator_ = Allocator{}) { + if (bool(callable)) { + vtable_t::init(vtable_, + type_erasure::make_box( + std::integral_constant{}, + std::forward(callable), + std::forward(allocator_)), + this->opaque_ptr(), capacity()); + } else { + vtable_.set_empty(); + } + } + + ~erasure() { + vtable_.weak_destroy(this->opaque_ptr(), capacity()); + } + + FU2_DETAIL_CXX14_CONSTEXPR erasure& + operator=(std::nullptr_t) noexcept(Property::is_strong_exception_guaranteed) { + vtable_.destroy(this->opaque_ptr(), capacity()); + return *this; + } + + FU2_DETAIL_CXX14_CONSTEXPR erasure& operator=(erasure&& right) noexcept( + Property::is_strong_exception_guaranteed) { + vtable_.weak_destroy(this->opaque_ptr(), capacity()); + right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(), + this->opaque_ptr(), capacity()); + return *this; + } + + FU2_DETAIL_CXX14_CONSTEXPR erasure& operator=(erasure const& right) { + vtable_.weak_destroy(this->opaque_ptr(), capacity()); + right.vtable_.copy(vtable_, right.opaque_ptr(), right.capacity(), + this->opaque_ptr(), capacity()); + return *this; + } + + template + FU2_DETAIL_CXX14_CONSTEXPR erasure& + operator=(erasure right) noexcept( + Property::is_strong_exception_guaranteed) { + vtable_.weak_destroy(this->opaque_ptr(), capacity()); + right.vtable_.move(vtable_, right.opaque_ptr(), right.capacity(), + this->opaque_ptr(), capacity()); + return *this; + } + + template >> + void assign(std::false_type /*use_bool_op*/, T&& callable, + Allocator&& allocator_ = {}) { + vtable_.weak_destroy(this->opaque_ptr(), capacity()); + vtable_t::init(vtable_, + type_erasure::make_box( + std::integral_constant{}, + std::forward(callable), + std::forward(allocator_)), + this->opaque_ptr(), capacity()); + } + + template >> + void assign(std::true_type /*use_bool_op*/, T&& callable, + Allocator&& allocator_ = {}) { + if (bool(callable)) { + assign(std::false_type{}, std::forward(callable), + std::forward(allocator_)); + } else { + operator=(nullptr); + } + } + + /// Returns true when the erasure doesn't hold any erased object + constexpr bool empty() const noexcept { + return vtable_.empty(); + } + + /// Invoke the function of the erasure at the given index + /// + /// We define this out of class to be able to forward the qualified + /// erasure correctly. + template + static constexpr decltype(auto) invoke(Erasure&& erasure, Args&&... args) { + auto const capacity = erasure.capacity(); + return erasure.vtable_.template invoke( + std::forward(erasure).opaque_ptr(), capacity, + std::forward(args)...); + } +}; + +// A non owning erasure +template +class erasure> { + template + friend class erasure; + template + friend class operator_impl; + + using property_t = property; + + using invoke_table_t = invocation_table::invoke_table; + typename invoke_table_t::type invoke_table_; + + /// The internal pointer to the non owned object + data_accessor view_; + +public: + // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) + constexpr erasure() noexcept + : invoke_table_( + invoke_table_t::template get_empty_invocation_table()), + view_(nullptr) { + } + + // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) + constexpr erasure(std::nullptr_t) noexcept + : invoke_table_( + invoke_table_t::template get_empty_invocation_table()), + view_(nullptr) { + } + + // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) + constexpr erasure(erasure&& right) noexcept + : invoke_table_(right.invoke_table_), view_(right.view_) { + } + + constexpr erasure(erasure const& /*right*/) = default; + + template + // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) + constexpr erasure(erasure right) noexcept + : invoke_table_(right.invoke_table_), view_(right.view_) { + } + + template + // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) + constexpr erasure(std::false_type /*use_bool_op*/, T&& object) + : invoke_table_(invoke_table_t::template get_invocation_view_table_of< + std::decay_t>()), + view_(address_taker>::take(std::forward(object))) { + } + template + // NOLINTNEXTLINE(cppcoreguidlines-pro-type-member-init) + FU2_DETAIL_CXX14_CONSTEXPR erasure(std::true_type use_bool_op, T&& object) { + this->assign(use_bool_op, std::forward(object)); + } + + ~erasure() = default; + + constexpr erasure& + operator=(std::nullptr_t) noexcept(HasStrongExceptGuarantee) { + invoke_table_ = + invoke_table_t::template get_empty_invocation_table(); + view_.ptr_ = nullptr; + return *this; + } + + constexpr erasure& operator=(erasure&& right) noexcept { + invoke_table_ = right.invoke_table_; + view_ = right.view_; + right = nullptr; + return *this; + } + + constexpr erasure& operator=(erasure const& /*right*/) = default; + + template + constexpr erasure& + operator=(erasure right) noexcept { + invoke_table_ = right.invoke_table_; + view_ = right.view_; + return *this; + } + + template + constexpr void assign(std::false_type /*use_bool_op*/, T&& callable) { + invoke_table_ = invoke_table_t::template get_invocation_view_table_of< + std::decay_t>(); + view_.ptr_ = + address_taker>::take(std::forward(callable)); + } + template + constexpr void assign(std::true_type /*use_bool_op*/, T&& callable) { + if (bool(callable)) { + assign(std::false_type{}, std::forward(callable)); + } else { + operator=(nullptr); + } + } + + /// Returns true when the erasure doesn't hold any erased object + constexpr bool empty() const noexcept { + return view_.ptr_ == nullptr; + } + + template + static constexpr decltype(auto) invoke(Erasure&& erasure, T&&... args) { + auto thunk = invoke_table_t::template fetch(erasure.invoke_table_); + return thunk(&(erasure.view_), 0UL, std::forward(args)...); + } +}; +} // namespace type_erasure + +/// Deduces to a true_type if the type T provides the given signature and the +/// signature is noexcept correct callable. +template > +struct accepts_one + : detail::lazy_and< // both are std::integral_constant + invocation::can_invoke, + typename Trait::arguments>, + invocation::is_noexcept_correct, + typename Trait::arguments>> {}; + +/// Deduces to a true_type if the type T provides all signatures +template +struct accepts_all : std::false_type {}; +template +struct accepts_all< + T, identity, + void_t::value>...>> + : std::true_type {}; + +#if defined(FU2_HAS_NO_EMPTY_PROPAGATION) +template +struct use_bool_op : std::false_type {}; +#elif defined(FU2_HAS_LIMITED_EMPTY_PROPAGATION) +/// Implementation for use_bool_op based on the behaviour of std::function, +/// propagating empty state for pointers, `std::function` and +/// `fu2::detail::function` types only. +template +struct use_bool_op : std::false_type {}; + +#if !defined(FU2_HAS_NO_FUNCTIONAL_HEADER) +template +struct use_bool_op> : std::true_type {}; +#endif + +template +struct use_bool_op> : std::true_type {}; + +template +struct use_bool_op : std::true_type {}; + +template +struct use_bool_op : std::true_type {}; +#else +template +struct has_bool_op : std::false_type {}; +template +struct has_bool_op()))>> + : std::true_type { +#ifndef NDEBUG + static_assert(!std::is_pointer::value, + "Missing deduction for function pointer!"); +#endif +}; + +/// Deduces to a true_type if the type T is implementing operator bool() +/// or if the type is convertible to bool directly, this also implements an +/// optimizations for function references `void(&)()` which are can never +/// be null and for such a conversion to bool would never return false. +template +struct use_bool_op : has_bool_op {}; + +#define FU2_DEFINE_USE_OP_TRAIT(CONST, VOLATILE, NOEXCEPT) \ + template \ + struct use_bool_op \ + : std::true_type {}; + +FU2_DETAIL_EXPAND_CV(FU2_DEFINE_USE_OP_TRAIT) +#undef FU2_DEFINE_USE_OP_TRAIT + +template +struct use_bool_op : std::false_type {}; + +#if defined(FU2_HAS_CXX17_NOEXCEPT_FUNCTION_TYPE) +template +struct use_bool_op : std::false_type {}; +#endif +#endif // FU2_HAS_NO_EMPTY_PROPAGATION + +template +struct assert_wrong_copy_assign { + static_assert(!Config::is_owning || !Config::is_copyable || + std::is_copy_constructible>::value, + "Can't wrap a non copyable object into a unique function!"); + + using type = void; +}; + +template +struct assert_no_strong_except_guarantee { + static_assert( + !IsStrongExceptGuaranteed || + (std::is_nothrow_move_constructible::value && + std::is_nothrow_destructible::value), + "Can't wrap a object an object that has no strong exception guarantees " + "if this is required by the wrapper!"); + + using type = void; +}; + +/// SFINAES out if the given callable is not copyable correct to the left one. +template +using enable_if_copyable_correct_t = + std::enable_if_t<(!LeftConfig::is_copyable || RightConfig::is_copyable)>; + +template +using is_owning_correct = + std::integral_constant; + +/// SFINAES out if the given function2 is not owning correct to this one +template +using enable_if_owning_correct_t = + std::enable_if_t::value>; + +template +class function> + : type_erasure::invocation_table::operator_impl< + 0U, + function>, + Args...> { + + template + friend class function; + + template + friend class type_erasure::invocation_table::operator_impl; + + using property_t = property; + using erasure_t = + type_erasure::erasure; + + template + using enable_if_can_accept_all_t = + std::enable_if_t, identity>::value>; + + template + struct is_convertible_to_this : std::false_type {}; + template + struct is_convertible_to_this< + function, + void_t, + enable_if_owning_correct_t>> + : std::true_type {}; + + template + using enable_if_not_convertible_to_this = + std::enable_if_t>::value>; + + template + using enable_if_owning_t = + std::enable_if_t::value && Config::is_owning>; + + template + using assert_wrong_copy_assign_t = + typename assert_wrong_copy_assign>::type; + + template + using assert_no_strong_except_guarantee_t = + typename assert_no_strong_except_guarantee>::type; + + erasure_t erasure_; + +public: + /// Default constructor which empty constructs the function + function() = default; + ~function() = default; + + explicit FU2_DETAIL_CXX14_CONSTEXPR + function(function const& /*right*/) = default; + explicit FU2_DETAIL_CXX14_CONSTEXPR function(function&& /*right*/) = default; + + /// Copy construction from another copyable function + template * = nullptr, + enable_if_copyable_correct_t* = nullptr, + enable_if_owning_correct_t* = nullptr> + FU2_DETAIL_CXX14_CONSTEXPR + function(function const& right) + : erasure_(right.erasure_) { + } + + /// Move construction from another function + template * = nullptr, + enable_if_owning_correct_t* = nullptr> + FU2_DETAIL_CXX14_CONSTEXPR function(function&& right) + : erasure_(std::move(right.erasure_)) { + } + + /// Construction from a callable object which overloads the `()` operator + template * = nullptr, + enable_if_can_accept_all_t* = nullptr, + assert_wrong_copy_assign_t* = nullptr, + assert_no_strong_except_guarantee_t* = nullptr> + FU2_DETAIL_CXX14_CONSTEXPR function(T&& callable) + : erasure_(use_bool_op>{}, std::forward(callable)) { + } + template * = nullptr, + enable_if_can_accept_all_t* = nullptr, + enable_if_owning_t* = nullptr, + assert_wrong_copy_assign_t* = nullptr, + assert_no_strong_except_guarantee_t* = nullptr> + FU2_DETAIL_CXX14_CONSTEXPR function(T&& callable, Allocator&& allocator_) + : erasure_(use_bool_op>{}, std::forward(callable), + std::forward(allocator_)) { + } + + /// Empty constructs the function + FU2_DETAIL_CXX14_CONSTEXPR function(std::nullptr_t np) : erasure_(np) { + } + + function& operator=(function const& /*right*/) = default; + function& operator=(function&& /*right*/) = default; + + /// Copy assigning from another copyable function + template * = nullptr, + enable_if_copyable_correct_t* = nullptr, + enable_if_owning_correct_t* = nullptr> + function& operator=(function const& right) { + erasure_ = right.erasure_; + return *this; + } + + /// Move assigning from another function + template * = nullptr, + enable_if_owning_correct_t* = nullptr> + function& operator=(function&& right) { + erasure_ = std::move(right.erasure_); + return *this; + } + + /// Move assigning from a callable object + template * = nullptr, + enable_if_can_accept_all_t* = nullptr, + assert_wrong_copy_assign_t* = nullptr, + assert_no_strong_except_guarantee_t* = nullptr> + function& operator=(T&& callable) { + erasure_.assign(use_bool_op>{}, std::forward(callable)); + return *this; + } + + /// Clears the function + function& operator=(std::nullptr_t np) { + erasure_ = np; + return *this; + } + + /// Returns true when the function is empty + bool empty() const noexcept { + return erasure_.empty(); + } + + /// Returns true when the function isn't empty + explicit operator bool() const noexcept { + return !empty(); + } + + /// Assigns a new target with an optional allocator + template >, + enable_if_not_convertible_to_this* = nullptr, + enable_if_can_accept_all_t* = nullptr, + assert_wrong_copy_assign_t* = nullptr, + assert_no_strong_except_guarantee_t* = nullptr> + void assign(T&& callable, Allocator&& allocator_ = Allocator{}) { + erasure_.assign(use_bool_op>{}, std::forward(callable), + std::forward(allocator_)); + } + + /// Swaps this function with the given function + void swap(function& other) noexcept(HasStrongExceptGuarantee) { + if (&other == this) { + return; + } + + function cache = std::move(other); + other = std::move(*this); + *this = std::move(cache); + } + + /// Swaps the left function with the right one + friend void swap(function& left, + function& right) noexcept(HasStrongExceptGuarantee) { + left.swap(right); + } + + /// Calls the wrapped callable object + using type_erasure::invocation_table::operator_impl< + 0U, function, Args...>::operator(); +}; + +template +bool operator==(function const& f, std::nullptr_t) { + return !bool(f); +} + +template +bool operator!=(function const& f, std::nullptr_t) { + return bool(f); +} + +template +bool operator==(std::nullptr_t, function const& f) { + return !bool(f); +} + +template +bool operator!=(std::nullptr_t, function const& f) { + return bool(f); +} + +// Default intended object size of the function +using object_size = std::integral_constant; +} // namespace detail +} // namespace abi_400 + +/// Can be passed to function_base as template argument which causes +/// the internal small buffer to be sized according to the given size, +/// and aligned with the given alignment. +template +struct capacity_fixed { + static constexpr std::size_t capacity = Capacity; + static constexpr std::size_t alignment = Alignment; +}; + +/// Default capacity for small functor optimization +struct capacity_default + : capacity_fixed {}; + +/// Can be passed to function_base as template argument which causes +/// the internal small buffer to be removed from the callable wrapper. +/// The owning function_base will then allocate memory for every object +/// it applies a type erasure on. +struct capacity_none : capacity_fixed<0UL> {}; + +/// Can be passed to function_base as template argument which causes +/// the internal small buffer to be sized such that it can hold +/// the given object without allocating memory for an applied type erasure. +template +struct capacity_can_hold { + static constexpr std::size_t capacity = sizeof(T); + static constexpr std::size_t alignment = alignof(T); +}; + +/// An adaptable function wrapper base for arbitrary functional types. +/// +/// \tparam IsOwning Is true when the type erasure shall be owning the object. +/// +/// \tparam IsCopyable Defines whether the function is copyable or not +/// +/// \tparam Capacity Defines the internal capacity of the function +/// for small functor optimization. +/// The size of the whole function object will be the capacity +/// plus the size of two pointers. If the capacity is zero, +/// the size will increase through one additional pointer +/// so the whole object has the size of 3 * sizeof(void*). +/// The type which is passed to the Capacity template parameter +/// shall provide a capacity and alignment member which +/// looks like the following example: +/// ```cpp +/// struct my_capacity { +/// static constexpr std::size_t capacity = sizeof(my_type); +/// static constexpr std::size_t alignment = alignof(my_type); +/// }; +/// ``` +/// +/// \tparam IsThrowing Defines whether the function throws an exception on +/// empty function call, `std::abort` is called otherwise. +/// +/// \tparam HasStrongExceptGuarantee Defines whether all objects satisfy the +/// strong exception guarantees, +/// which means the function type will satisfy +/// the strong exception guarantees too. +/// +/// \tparam Signatures Defines the signature of the callable wrapper +/// +template +using function_base = detail::function< + detail::config, + detail::property>; + +/// An owning copyable function wrapper for arbitrary callable types. +template +using function = function_base; + +/// An owning non copyable function wrapper for arbitrary callable types. +template +using unique_function = function_base; + +/// A non owning copyable function wrapper for arbitrary callable types. +template +using function_view = function_base; + +#if !defined(FU2_HAS_DISABLED_EXCEPTIONS) +/// Exception type that is thrown when invoking empty function objects +/// and exception support isn't disabled. +/// +/// Exception support is enabled if +/// the template parameter 'Throwing' is set to true (default). +/// +/// This type will default to std::bad_function_call if the +/// functional header is used, otherwise the library provides its own type. +/// +/// You may disable the inclusion of the functional header +/// through defining `FU2_WITH_NO_FUNCTIONAL_HEADER`. +/// +using detail::type_erasure::invocation_table::bad_function_call; +#endif + +/// Returns a callable object, which unifies all callable objects +/// that were passed to this function. +/// +/// ```cpp +/// auto overloaded = fu2::overload([](std::true_type) { return true; }, +/// [](std::false_type) { return false; }); +/// ``` +/// +/// \param callables A pack of callable objects with arbitrary signatures. +/// +/// \returns A callable object which exposes the +/// +template +constexpr auto overload(T&&... callables) { + return detail::overloading::overload(std::forward(callables)...); +} +} // namespace fu2 + +#undef FU2_DETAIL_EXPAND_QUALIFIERS +#undef FU2_DETAIL_EXPAND_QUALIFIERS_NOEXCEPT +#undef FU2_DETAIL_EXPAND_CV +#undef FU2_DETAIL_EXPAND_CV_NOEXCEPT +#undef FU2_DETAIL_UNREACHABLE_INTRINSIC +#undef FU2_DETAIL_TRAP +#undef FU2_DETAIL_CXX14_CONSTEXPR + +#endif // FU2_INCLUDED_FUNCTION2_HPP_