diff --git a/.github/workflows/bvt-appleclang.yml b/.github/workflows/bvt-appleclang.yml index 2aebf90..d3b84b9 100644 --- a/.github/workflows/bvt-appleclang.yml +++ b/.github/workflows/bvt-appleclang.yml @@ -20,6 +20,10 @@ jobs: - name: build and run test with AppleClang run: | - cmake -B build + cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j ctest --test-dir build -j + + - name: run benchmarks + run: | + ./build/benchmarks/msft_proxy_benchmarks --benchmark_repetitions=10 --benchmark_report_aggregates_only=true diff --git a/.github/workflows/bvt-clang.yml b/.github/workflows/bvt-clang.yml index 7dea031..e1b5265 100644 --- a/.github/workflows/bvt-clang.yml +++ b/.github/workflows/bvt-clang.yml @@ -26,24 +26,28 @@ jobs: - name: build and run test with clang 15 run: | - cmake -B build-clang-15 -DCMAKE_C_COMPILER=clang-15 -DCMAKE_CXX_COMPILER=clang++-15 + cmake -B build-clang-15 -DCMAKE_C_COMPILER=clang-15 -DCMAKE_CXX_COMPILER=clang++-15 -DCMAKE_BUILD_TYPE=Release cmake --build build-clang-15 -j ctest --test-dir build-clang-15 -j - name: build and run test with clang 16 run: | - cmake -B build-clang-16 -DCMAKE_C_COMPILER=clang-16 -DCMAKE_CXX_COMPILER=clang++-16 + cmake -B build-clang-16 -DCMAKE_C_COMPILER=clang-16 -DCMAKE_CXX_COMPILER=clang++-16 -DCMAKE_BUILD_TYPE=Release cmake --build build-clang-16 -j ctest --test-dir build-clang-16 -j - name: build and run test with clang 17 run: | - cmake -B build-clang-17 -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 + cmake -B build-clang-17 -DCMAKE_C_COMPILER=clang-17 -DCMAKE_CXX_COMPILER=clang++-17 -DCMAKE_BUILD_TYPE=Release cmake --build build-clang-17 -j ctest --test-dir build-clang-17 -j - name: build and run test with clang 18 run: | - cmake -B build-clang-18 -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 + cmake -B build-clang-18 -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 -DCMAKE_BUILD_TYPE=Release cmake --build build-clang-18 -j ctest --test-dir build-clang-18 -j + + - name: run benchmarks + run: | + ./build-clang-18/benchmarks/msft_proxy_benchmarks --benchmark_repetitions=10 --benchmark_report_aggregates_only=true diff --git a/.github/workflows/bvt-gcc.yml b/.github/workflows/bvt-gcc.yml index 142dfa6..fa15d1d 100644 --- a/.github/workflows/bvt-gcc.yml +++ b/.github/workflows/bvt-gcc.yml @@ -26,24 +26,28 @@ jobs: - name: build and run test with gcc 11 run: | - cmake -B build-gcc-11 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 + cmake -B build-gcc-11 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_BUILD_TYPE=Release cmake --build build-gcc-11 -j ctest --test-dir build-gcc-11 -j - name: build and run test with gcc 12 run: | - cmake -B build-gcc-12 -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 + cmake -B build-gcc-12 -DCMAKE_C_COMPILER=gcc-12 -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_BUILD_TYPE=Release cmake --build build-gcc-12 -j ctest --test-dir build-gcc-12 -j - name: build and run test with gcc 13 run: | - cmake -B build-gcc-13 -DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13 + cmake -B build-gcc-13 -DCMAKE_C_COMPILER=gcc-13 -DCMAKE_CXX_COMPILER=g++-13 -DCMAKE_BUILD_TYPE=Release cmake --build build-gcc-13 -j ctest --test-dir build-gcc-13 -j - name: build and run test with gcc 14 run: | - cmake -B build-gcc-14 -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 + cmake -B build-gcc-14 -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_BUILD_TYPE=Release cmake --build build-gcc-14 -j ctest --test-dir build-gcc-14 -j + + - name: run benchmarks + run: | + ./build-gcc-14/benchmarks/msft_proxy_benchmarks --benchmark_repetitions=10 --benchmark_report_aggregates_only=true diff --git a/.github/workflows/bvt-msvc.yml b/.github/workflows/bvt-msvc.yml index 8c69492..0972dc0 100644 --- a/.github/workflows/bvt-msvc.yml +++ b/.github/workflows/bvt-msvc.yml @@ -13,11 +13,12 @@ jobs: with: ref: ${{ inputs.branch }} - - name: build with cmake + - name: build and run test with MSVC run: | cmake -B build - cmake --build build -j + cmake --build build --config Release -j + ctest --test-dir build -j - - name: run tests + - name: run benchmarks run: | - ctest --test-dir build -j + .\build\benchmarks\Release\msft_proxy_benchmarks.exe --benchmark_repetitions=10 --benchmark_report_aggregates_only=true diff --git a/CMakeLists.txt b/CMakeLists.txt index 89e3b4b..51ced73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,21 +26,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/proxyConfigVersion.cmake # build tests if BUILD_TESTING is ON include(CTest) if (BUILD_TESTING) - include(FetchContent) - # The policy uses the download time for timestamp, instead of the timestamp in the archive. This - # allows for proper rebuilds when a projects URL changes. - if(POLICY CMP0135) - cmake_policy(SET CMP0135 NEW) - endif() - FetchContent_Declare( - googletest - URL https://github.com/google/googletest/releases/download/v1.15.2/googletest-1.15.2.tar.gz - ) - - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # For Windows: Prevent overriding the parent project's compiler/linker settings - set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) # Disable GMock - FetchContent_MakeAvailable(googletest) - add_subdirectory(tests) + add_subdirectory(benchmarks) add_subdirectory(samples) endif() diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 0000000..3b500d1 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,31 @@ +project(msft_proxy_benchmarks) + +include(FetchContent) +# The policy uses the download time for timestamp, instead of the timestamp in the archive. This +# allows for proper rebuilds when a projects URL changes. +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() + +FetchContent_Declare( + benchmark + URL https://github.com/google/benchmark/archive/refs/tags/v1.9.0.tar.gz + URL_HASH SHA256=35a77f46cc782b16fac8d3b107fbfbb37dcd645f7c28eee19f3b8e0758b48994 +) +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Disable tests for google benchmark") +set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "Disable google benchmark unit tests") +FetchContent_MakeAvailable(benchmark) + +add_executable(msft_proxy_benchmarks + proxy_invocation_benchmark_context.cpp + proxy_invocation_benchmark.cpp + proxy_management_benchmark.cpp +) +target_include_directories(msft_proxy_benchmarks PRIVATE .) +target_link_libraries(msft_proxy_benchmarks PRIVATE msft_proxy benchmark::benchmark benchmark::benchmark_main) + +if (MSVC) + target_compile_options(msft_proxy_benchmarks PRIVATE /W4) +else() + target_compile_options(msft_proxy_benchmarks PRIVATE -Wall -Wextra -Wpedantic) +endif() diff --git a/benchmarks/proxy_invocation_benchmark.cpp b/benchmarks/proxy_invocation_benchmark.cpp new file mode 100644 index 0000000..29f85a3 --- /dev/null +++ b/benchmarks/proxy_invocation_benchmark.cpp @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include + +#include "proxy_invocation_benchmark_context.h" + +void BM_SmallObjectInvocationViaProxy(benchmark::State& state) { + for (auto _ : state) { + for (auto& p : SmallObjectInvocationProxyTestData) { + int result = p->Fun(); + benchmark::DoNotOptimize(result); + } + } +} + +void BM_SmallObjectInvocationViaVirtualFunction(benchmark::State& state) { + for (auto _ : state) { + for (auto& p : SmallObjectInvocationVirtualFunctionTestData) { + int result = p->Fun(); + benchmark::DoNotOptimize(result); + } + } +} + +void BM_LargeObjectInvocationViaProxy(benchmark::State& state) { + for (auto _ : state) { + for (auto& p : LargeObjectInvocationProxyTestData) { + int result = p->Fun(); + benchmark::DoNotOptimize(result); + } + } +} + +void BM_LargeObjectInvocationViaVirtualFunction(benchmark::State& state) { + for (auto _ : state) { + for (auto& p : LargeObjectInvocationVirtualFunctionTestData) { + int result = p->Fun(); + benchmark::DoNotOptimize(result); + } + } +} + +BENCHMARK(BM_SmallObjectInvocationViaProxy); +BENCHMARK(BM_SmallObjectInvocationViaVirtualFunction); +BENCHMARK(BM_LargeObjectInvocationViaProxy); +BENCHMARK(BM_LargeObjectInvocationViaVirtualFunction); diff --git a/benchmarks/proxy_invocation_benchmark_context.cpp b/benchmarks/proxy_invocation_benchmark_context.cpp new file mode 100644 index 0000000..c39d38c --- /dev/null +++ b/benchmarks/proxy_invocation_benchmark_context.cpp @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "proxy_invocation_benchmark_context.h" + +namespace { + +constexpr int TestDataSize = 1000000; +constexpr int TypeSeriesCount = 100; + +template +class NonIntrusiveSmallImpl { + public: + explicit NonIntrusiveSmallImpl(int seed) noexcept : seed_(seed) {} + NonIntrusiveSmallImpl(const NonIntrusiveSmallImpl&) noexcept = default; + int Fun() const noexcept { return seed_ ^ (TypeSeries + 1); } + + private: + int seed_; +}; + +template +class NonIntrusiveLargeImpl { + public: + explicit NonIntrusiveLargeImpl(int seed) noexcept : seed_(seed) {} + NonIntrusiveLargeImpl(const NonIntrusiveLargeImpl&) noexcept = default; + int Fun() const noexcept { return seed_ ^ (TypeSeries + 1); } + + private: + void* padding_[15]{}; + int seed_; +}; + +template +class IntrusiveSmallImpl : public InvocationTestBase { + public: + explicit IntrusiveSmallImpl(int seed) noexcept : seed_(seed) {} + IntrusiveSmallImpl(const IntrusiveSmallImpl&) noexcept = default; + int Fun() const noexcept override { return seed_ ^ (TypeSeries + 1); } + + private: + int seed_; +}; + +template +class IntrusiveLargeImpl : public InvocationTestBase { + public: + explicit IntrusiveLargeImpl(int seed) noexcept : seed_(seed) {} + IntrusiveLargeImpl(const IntrusiveLargeImpl&) noexcept = default; + int Fun() const noexcept override { return seed_ ^ (TypeSeries + 1); } + + private: + void* padding_[16]{}; + int seed_; +}; + +template +struct IntConstant {}; + +template +void FillTestData(std::vector& data, const F& generator) { + if constexpr (FromTypeSeries < TypeSeriesCount) { + for (int i = FromTypeSeries; i < TestDataSize; i += TypeSeriesCount) { + data[i] = generator(IntConstant{}, i); + } + FillTestData(data, generator); + } +} + +template +auto GenerateTestData(const F& generator) { + std::vector{}, 0))> result(TestDataSize); + FillTestData<0>(result, generator); + return result; +} + +} // namespace + +const std::vector> SmallObjectInvocationProxyTestData = GenerateTestData( + [](IntConstant, int seed) + { return pro::make_proxy>(seed); }); + +const std::vector> SmallObjectInvocationVirtualFunctionTestData = GenerateTestData( + [](IntConstant, int seed) + { return std::unique_ptr{new IntrusiveSmallImpl(seed)}; }); + +const std::vector> LargeObjectInvocationProxyTestData = GenerateTestData( + [](IntConstant, int seed) + { return pro::make_proxy>(seed); }); + +const std::vector> LargeObjectInvocationVirtualFunctionTestData = GenerateTestData( + [](IntConstant, int seed) + { return std::unique_ptr{new IntrusiveLargeImpl(seed)}; }); diff --git a/benchmarks/proxy_invocation_benchmark_context.h b/benchmarks/proxy_invocation_benchmark_context.h new file mode 100644 index 0000000..2f4bdd5 --- /dev/null +++ b/benchmarks/proxy_invocation_benchmark_context.h @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include +#include + +#include "proxy.h" + +PRO_DEF_MEM_DISPATCH(MemFun, Fun); + +struct InvocationTestFacade : pro::facade_builder + ::add_convention + ::build{}; + +struct InvocationTestBase { + virtual int Fun() const = 0; + virtual ~InvocationTestBase() = default; +}; + +extern const std::vector> SmallObjectInvocationProxyTestData; +extern const std::vector> SmallObjectInvocationVirtualFunctionTestData; +extern const std::vector> LargeObjectInvocationProxyTestData; +extern const std::vector> LargeObjectInvocationVirtualFunctionTestData; diff --git a/benchmarks/proxy_management_benchmark.cpp b/benchmarks/proxy_management_benchmark.cpp new file mode 100644 index 0000000..07d05fa --- /dev/null +++ b/benchmarks/proxy_management_benchmark.cpp @@ -0,0 +1,209 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include +#include +#include +#include +#include + +#include + +#include "proxy.h" + +namespace { + +constexpr int TestManagedObjectCount = 120000; +constexpr int TypeSeriesCount = 3; + +using SmallObject1 = int; +using SmallObject2 = std::shared_ptr; +struct SmallObject3 { + SmallObject3() noexcept = default; + SmallObject3(SmallObject3&&) noexcept = default; + SmallObject3(const SmallObject3&) { throw std::runtime_error{ "Not implemented" }; } + + std::unique_lock Field1; +}; + +using LargeObject1 = std::array; +using LargeObject2 = std::array; +struct LargeObject3 { + SmallObject3 Field1; + void* Padding[15]; +}; + +struct PolymorphicObjectBase { + virtual ~PolymorphicObjectBase() = default; +}; +template +struct PolymorphicObject : PolymorphicObjectBase { + T Value; +}; + +} // namespace + +struct DefaultFacade : pro::facade_builder + ::support_copy + ::build {}; + +void BM_SmallObjectManagementWithProxy(benchmark::State& state) { + for (auto _ : state) { + std::vector> data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.push_back(pro::make_proxy()); + data.push_back(pro::make_proxy()); + data.push_back(pro::make_proxy()); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_SmallObjectManagementWithUniquePtr(benchmark::State& state) { + for (auto _ : state) { + std::vector> data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.push_back(std::unique_ptr{new PolymorphicObject()}); + data.push_back(std::unique_ptr{new PolymorphicObject()}); + data.push_back(std::unique_ptr{new PolymorphicObject()}); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_SmallObjectManagementWithSharedPtr(benchmark::State& state) { + for (auto _ : state) { + std::vector> data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.emplace_back(std::make_shared()); + data.emplace_back(std::make_shared()); + data.emplace_back(std::make_shared()); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_SmallObjectManagementWithSharedPtr_Pooled(benchmark::State& state) { + std::pmr::unsynchronized_pool_resource pool; + std::pmr::polymorphic_allocator<> alloc{&pool}; + for (auto _ : state) { + std::vector> data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.emplace_back(std::allocate_shared(alloc)); + data.emplace_back(std::allocate_shared(alloc)); + data.emplace_back(std::allocate_shared(alloc)); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_SmallObjectManagementWithAny(benchmark::State& state) { + for (auto _ : state) { + std::vector data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.emplace_back(SmallObject1{}); + data.emplace_back(SmallObject2{}); + data.emplace_back(SmallObject3{}); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_LargeObjectManagementWithProxy(benchmark::State& state) { + for (auto _ : state) { + std::vector> data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.push_back(pro::make_proxy()); + data.push_back(pro::make_proxy()); + data.push_back(pro::make_proxy()); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_LargeObjectManagementWithProxy_Pooled(benchmark::State& state) { + std::pmr::unsynchronized_pool_resource pool; + std::pmr::polymorphic_allocator<> alloc{&pool}; + for (auto _ : state) { + std::vector> data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.push_back(pro::allocate_proxy(alloc)); + data.push_back(pro::allocate_proxy(alloc)); + data.push_back(pro::allocate_proxy(alloc)); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_LargeObjectManagementWithUniquePtr(benchmark::State& state) { + for (auto _ : state) { + std::vector> data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.push_back(std::unique_ptr{new PolymorphicObject()}); + data.push_back(std::unique_ptr{new PolymorphicObject()}); + data.push_back(std::unique_ptr{new PolymorphicObject()}); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_LargeObjectManagementWithSharedPtr(benchmark::State& state) { + for (auto _ : state) { + std::vector> data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.emplace_back(std::make_shared()); + data.emplace_back(std::make_shared()); + data.emplace_back(std::make_shared()); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_LargeObjectManagementWithSharedPtr_Pooled(benchmark::State& state) { + std::pmr::unsynchronized_pool_resource pool; + std::pmr::polymorphic_allocator<> alloc{&pool}; + for (auto _ : state) { + std::vector> data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.emplace_back(std::allocate_shared(alloc)); + data.emplace_back(std::allocate_shared(alloc)); + data.emplace_back(std::allocate_shared(alloc)); + } + benchmark::DoNotOptimize(data); + } +} + +void BM_LargeObjectManagementWithAny(benchmark::State& state) { + for (auto _ : state) { + std::vector data; + data.reserve(TestManagedObjectCount); + for (int i = 0; i < TestManagedObjectCount; i += TypeSeriesCount) { + data.emplace_back(LargeObject1{}); + data.emplace_back(LargeObject2{}); + data.emplace_back(LargeObject3{}); + } + benchmark::DoNotOptimize(data); + } +} + +BENCHMARK(BM_SmallObjectManagementWithProxy); +BENCHMARK(BM_SmallObjectManagementWithUniquePtr); +BENCHMARK(BM_SmallObjectManagementWithSharedPtr); +BENCHMARK(BM_SmallObjectManagementWithSharedPtr_Pooled); +BENCHMARK(BM_SmallObjectManagementWithAny); +BENCHMARK(BM_LargeObjectManagementWithProxy); +BENCHMARK(BM_LargeObjectManagementWithProxy_Pooled); +BENCHMARK(BM_LargeObjectManagementWithUniquePtr); +BENCHMARK(BM_LargeObjectManagementWithSharedPtr); +BENCHMARK(BM_LargeObjectManagementWithSharedPtr_Pooled); +BENCHMARK(BM_LargeObjectManagementWithAny); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 49fa868..8d91ed1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,4 +1,21 @@ project(msft_proxy_tests) + +include(FetchContent) +# The policy uses the download time for timestamp, instead of the timestamp in the archive. This +# allows for proper rebuilds when a projects URL changes. +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() + +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/releases/download/v1.15.2/googletest-1.15.2.tar.gz + URL_HASH SHA256=7b42b4d6ed48810c5362c265a17faebe90dc2373c885e5216439d37927f02926 +) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # For Windows: Prevent overriding the parent project's compiler/linker settings +set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) # Disable GMock +FetchContent_MakeAvailable(googletest) + add_executable(msft_proxy_tests proxy_creation_tests.cpp proxy_dispatch_tests.cpp @@ -13,9 +30,9 @@ target_link_libraries(msft_proxy_tests PRIVATE msft_proxy) target_link_libraries(msft_proxy_tests PRIVATE gtest_main) if (MSVC) - target_compile_options(msft_proxy_tests PRIVATE /W4 /WX) + target_compile_options(msft_proxy_tests PRIVATE /W4) else() - target_compile_options(msft_proxy_tests PRIVATE -Wall -Wextra -Wpedantic -Werror) + target_compile_options(msft_proxy_tests PRIVATE -Wall -Wextra -Wpedantic) endif() include(GoogleTest)