diff --git a/CMakeLists.txt b/CMakeLists.txt index 1247d8219c..0d4acf141d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2367,6 +2367,19 @@ if(ENABLE_TESTS) add_library(GTest::gtest ALIAS gtest) add_library(GTest::gtest_main ALIAS gtest_main) endif() + + find_package(benchmark) + if (NOT benchmark_FOUND) + message(STATUS "benchmark package not found, will download benchmark automatically. To prevent that install google benchmark on your system (libbenchmark-dev)") + include(FetchContent) + set(BENCHMARK_ENABLE_TESTING OFF) + FetchContent_Declare( + benchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG v1.9.0 + ) + FetchContent_MakeAvailable(benchmark) + endif() endif() add_subdirectory(common) diff --git a/doc/UnitTests.md b/doc/UnitTests.md index d1bbbed0ae..c2e1285c4f 100644 --- a/doc/UnitTests.md +++ b/doc/UnitTests.md @@ -107,3 +107,65 @@ that directory directly, which you might also use to compare to the slight complication is due to using shell scripts. An easier way is to directly declare the executable in `add_test()`, and `ctest` will locate and run the executable properly. + +# Benchmarking + +Google benchmark can be used to profile and benchmark small pieces of code. See +`benchmark_rotate_vector` for reference implementation. To start benchmarking code, +write a benchmark first and compare your implementation against baseline result. +To ensure your results are reproducible see this [guide](https://github.com/google/benchmark/blob/main/docs/reducing_variance.md) + +Example output follows: + +```bash +2024-08-26T11:55:49+02:00 +Running ./openair1/PHY/TOOLS/tests/benchmark_rotate_vector +Run on (8 X 4700 MHz CPU s) +CPU Caches: + L1 Data 48 KiB (x4) + L1 Instruction 32 KiB (x4) + L2 Unified 1280 KiB (x4) + L3 Unified 12288 KiB (x1) +Load Average: 0.51, 0.31, 0.29 +***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead. +--------------------------------------------------------------------- +Benchmark Time CPU Iterations +--------------------------------------------------------------------- +BM_rotate_cpx_vector/100 43.1 ns 43.1 ns 16683136 +BM_rotate_cpx_vector/256 70.1 ns 70.0 ns 9647446 +BM_rotate_cpx_vector/1024 277 ns 277 ns 2378273 +BM_rotate_cpx_vector/4096 1070 ns 1070 ns 654792 +BM_rotate_cpx_vector/16384 4220 ns 4220 ns 169070 +BM_rotate_cpx_vector/20000 5288 ns 5289 ns 136190 +``` + +## Comparing results + +Benchmark results can be output to json by using command line arguments, example below + +```bash +./benchmark_rotate_vector --benchmark_out=file.json --benchmark_repetitions=10 +``` + +These results can be compared by a tool provided with google benchmark + +```bash +./compare.py benchmarks ../../file.json ../../file1.json +``` + +Example output: +``` +Comparing ../../file.json to ../../file1.json +Benchmark Time CPU Time Old Time New CPU Old CPU New +-------------------------------------------------------------------------------------------------------------------------------- +BM_rotate_cpx_vector/100 +0.3383 +0.3384 43 58 43 58 +BM_rotate_cpx_vector/100 +0.2334 +0.2335 42 52 42 52 +BM_rotate_cpx_vector/100 +0.1685 +0.1683 42 49 42 49 +BM_rotate_cpx_vector/100 +0.1890 +0.1889 42 50 42 50 +BM_rotate_cpx_vector/100 +0.0456 +0.0457 42 44 42 44 +BM_rotate_cpx_vector/100 +0.0163 +0.0162 42 42 42 42 +BM_rotate_cpx_vector/100 +0.0005 +0.0004 43 43 43 43 +BM_rotate_cpx_vector/100 +0.0134 +0.0129 43 43 43 43 +BM_rotate_cpx_vector/100 +0.0162 +0.0162 42 42 42 42 +BM_rotate_cpx_vector/100 +0.0003 +0.0003 42 42 42 42 +``` diff --git a/openair1/PHY/NR_TRANSPORT/tests/test_llr.cpp b/openair1/PHY/NR_TRANSPORT/tests/test_llr.cpp index 56a0943d03..5778bd6c39 100644 --- a/openair1/PHY/NR_TRANSPORT/tests/test_llr.cpp +++ b/openair1/PHY/NR_TRANSPORT/tests/test_llr.cpp @@ -56,18 +56,9 @@ void exit_function(const char *file, const char *function, const int line, const #include "common/utils/LOG/log.h" #include #include +#include "openair1/PHY/TOOLS/phy_test_tools.hpp" #include -constexpr bool is_power_of_two(uint64_t n) -{ - return n > 0 && (n & (n - 1)) == 0; -} - -size_t align_up(size_t a, size_t b) -{ - return (a + b - 1) / b * b; -} - int16_t saturating_sub(int16_t a, int16_t b) { int32_t result = (int32_t)a - (int32_t)b; @@ -81,44 +72,6 @@ int16_t saturating_sub(int16_t a, int16_t b) } } -// Template adaptations for std::vector. This is needed because the avx functions expect 256 bit alignment. -template -class AlignedAllocator { - public: - static_assert(is_power_of_two(alignment), "Alignment should be power of 2"); - static_assert(alignment >= 8, "Alignment must be at least 8 bits"); - using value_type = T; - - AlignedAllocator() = default; - - AlignedAllocator(const AlignedAllocator &) = default; - - AlignedAllocator &operator=(const AlignedAllocator &) = default; - - template - struct rebind { - using other = AlignedAllocator; - }; - - T *allocate(size_t n) - { - size_t alignment_bytes = alignment / 8; - void *ptr = ::aligned_alloc(alignment_bytes, align_up(n * sizeof(T), alignment_bytes)); - return static_cast(ptr); - } - - void deallocate(T *p, size_t n) - { - ::free(p); - } -}; - -// Using 512-aligned vector in case some functions use avx-512 -template -using AlignedAllocator512 = AlignedAllocator; -template -using AlignedVector512 = std::vector>; - void nr_ulsch_16qam_llr_ref(c16_t *rxdataF_comp, int32_t *ul_ch_mag, int16_t *ulsch_llr, uint32_t nb_re, uint8_t symbol) { int16_t *ul_ch_mag_i16 = (int16_t *)ul_ch_mag; @@ -190,27 +143,6 @@ void nr_ulsch_256qam_llr_ref(c16_t *rxdataF_comp, } } -AlignedVector512 generate_random_c16(size_t num) -{ - std::random_device rd; - std::mt19937 rng(rd()); - std::uniform_int_distribution dist(INT16_MIN, INT16_MAX); - AlignedVector512 vec; - vec.resize(num); - auto gen = [&]() { return (c16_t){dist(rng), dist(rng)}; }; - std::generate(vec.begin(), vec.end(), gen); - return vec; -} - -AlignedVector512 generate_random_uint16(size_t num) -{ - AlignedVector512 vec; - vec.resize(num); - auto gen = [&]() { return static_cast(std::rand()); }; - std::generate(vec.begin(), vec.end(), gen); - return vec; -} - void test_function_16_qam(AlignedVector512 nb_res) { for (auto i = 0U; i < nb_res.size(); i++) { diff --git a/openair1/PHY/TOOLS/phy_test_tools.hpp b/openair1/PHY/TOOLS/phy_test_tools.hpp new file mode 100644 index 0000000000..dcaa192dd4 --- /dev/null +++ b/openair1/PHY/TOOLS/phy_test_tools.hpp @@ -0,0 +1,101 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (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.openairinterface.org/?page_id=698 + * + * 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. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef __PHY_TEST_TOOLS_HPP__ +#define __PHY_TEST_TOOLS_HPP__ + +#include +#include +#include +extern "C" { +#include "openair1/PHY/TOOLS/tools_defs.h" +} + +constexpr bool is_power_of_two(uint64_t n) +{ + return n != 0 && (n & (n - 1)) == 0; +} + +size_t align_up(size_t a, size_t b) +{ + return (a + b - 1) / b * b; +} + +// Template adaptations for std::vector. This is needed because the avx functions expect 256 bit alignment. +template +class AlignedAllocator { + public: + static_assert(is_power_of_two(alignment), "Alignment should be power of 2"); + static_assert(alignment >= 8, "Alignment must be at least 8 bits"); + using value_type = T; + + AlignedAllocator() = default; + + AlignedAllocator(const AlignedAllocator &) = default; + + AlignedAllocator &operator=(const AlignedAllocator &) = default; + + template + struct rebind { + using other = AlignedAllocator; + }; + + T *allocate(size_t n) + { + size_t alignment_bytes = alignment / 8; + void *ptr = ::aligned_alloc(alignment_bytes, align_up(n * sizeof(T), alignment_bytes)); + return static_cast(ptr); + } + + void deallocate(T *p, size_t n) + { + ::free(p); + } +}; + +// Using 512-aligned vector in case some functions use avx-512 +template +using AlignedAllocator512 = AlignedAllocator; +template +using AlignedVector512 = std::vector>; + +AlignedVector512 generate_random_c16(size_t num) +{ + std::random_device rd; + std::mt19937 rng(rd()); + std::uniform_int_distribution dist(INT16_MIN, INT16_MAX); + AlignedVector512 vec; + vec.resize(num); + auto gen = [&]() { return (c16_t){dist(rng), dist(rng)}; }; + std::generate(vec.begin(), vec.end(), gen); + return vec; +} + +AlignedVector512 generate_random_uint16(size_t num) +{ + AlignedVector512 vec; + vec.resize(num); + auto gen = [&]() { return static_cast(std::rand()); }; + std::generate(vec.begin(), vec.end(), gen); + return vec; +} + +#endif diff --git a/openair1/PHY/TOOLS/tests/CMakeLists.txt b/openair1/PHY/TOOLS/tests/CMakeLists.txt index c2f7470916..2822e058fd 100644 --- a/openair1/PHY/TOOLS/tests/CMakeLists.txt +++ b/openair1/PHY/TOOLS/tests/CMakeLists.txt @@ -3,3 +3,9 @@ target_link_libraries(test_signal_energy PRIVATE GTest::gtest UTIL) add_dependencies(tests test_signal_energy) add_test(NAME test_signal_energy COMMAND ./test_signal_energy) + +add_executable(benchmark_rotate_vector benchmark_rotate_vector.cpp) +target_link_libraries(benchmark_rotate_vector PRIVATE benchmark::benchmark UTIL PHY_NR) +add_dependencies(tests benchmark_rotate_vector) +add_test(NAME benchmark_rotate_vector + COMMAND ./benchmark_rotate_vector) diff --git a/openair1/PHY/TOOLS/tests/benchmark_rotate_vector.cpp b/openair1/PHY/TOOLS/tests/benchmark_rotate_vector.cpp new file mode 100644 index 0000000000..6f4ea89643 --- /dev/null +++ b/openair1/PHY/TOOLS/tests/benchmark_rotate_vector.cpp @@ -0,0 +1,59 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (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.openairinterface.org/?page_id=698 + * + * 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. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include +#include +#include +extern "C" { +#include "openair1/PHY/TOOLS/tools_defs.h" +struct configmodule_interface_s; +struct configmodule_interface_s *uniqCfg = NULL; +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert) +{ + if (assert) { + abort(); + } else { + exit(EXIT_SUCCESS); + } +} +} +#include +#include "common/utils/LOG/log.h" +#include "benchmark/benchmark.h" +#include "openair1/PHY/TOOLS/phy_test_tools.hpp" + +static void BM_rotate_cpx_vector(benchmark::State &state) +{ + int vector_size = state.range(0); + auto input_complex_16 = generate_random_c16(vector_size); + auto input_alpha = generate_random_c16(vector_size); + AlignedVector512 output; + output.resize(vector_size); + int shift = 2; + for (auto _ : state) { + rotate_cpx_vector(input_complex_16.data(), input_alpha.data(), output.data(), vector_size, shift); + } +} + +BENCHMARK(BM_rotate_cpx_vector)->RangeMultiplier(4)->Range(100, 20000); + +BENCHMARK_MAIN();