From d51431a1c39d3740b9c91a827187ec09515b836f Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Wed, 2 Jul 2025 18:46:03 +0200 Subject: [PATCH 1/4] refactor CMake scripts: centralize target linking functionality --- CMakeLists.txt | 1 + cmake/gtest.cmake | 12 +++++++ cmake/json.cmake | 11 +++++++ cmake/libenvpp.cmake | 18 ++++++++++ cmake/mpi.cmake | 17 +++++++++- cmake/onetbb.cmake | 14 ++++++++ cmake/openmp.cmake | 10 ++++++ cmake/stb.cmake | 5 +++ modules/core/CMakeLists.txt | 65 +++++-------------------------------- 9 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 cmake/stb.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 88f343287..1a0982a27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,7 @@ include(cmake/modes.cmake) include(cmake/sanitizers.cmake) include(cmake/json.cmake) include(cmake/libenvpp.cmake) +include(cmake/stb.cmake) ################# Parallel programming technologies ################# diff --git a/cmake/gtest.cmake b/cmake/gtest.cmake index a9bdd6f13..eb41a7cd3 100644 --- a/cmake/gtest.cmake +++ b/cmake/gtest.cmake @@ -24,3 +24,15 @@ ExternalProject_Add( "${CMAKE_COMMAND}" --install "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/build" --prefix "${CMAKE_CURRENT_BINARY_DIR}/ppc_googletest/install") + +function(ppc_link_gtest exec_func_lib) + # Add external project include directories + target_include_directories( + ${exec_func_lib} + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/googletest/googletest/include) + + add_dependencies(${exec_func_lib} ppc_googletest) + target_link_directories(${exec_func_lib} PUBLIC + "${CMAKE_BINARY_DIR}/ppc_googletest/install/lib") + target_link_libraries(${exec_func_lib} PUBLIC gtest gtest_main) +endfunction() diff --git a/cmake/json.cmake b/cmake/json.cmake index 89070d4b7..882553058 100644 --- a/cmake/json.cmake +++ b/cmake/json.cmake @@ -19,3 +19,14 @@ ExternalProject_Add( INSTALL_COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_CURRENT_BINARY_DIR}/ppc_json/build" --prefix "${CMAKE_CURRENT_BINARY_DIR}/ppc_json/install") + +function(ppc_link_json exec_func_lib) + # Add external project include directories + target_include_directories( + ${exec_func_lib} + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/json/include) + + add_dependencies(${exec_func_lib} ppc_json) + target_link_directories(${exec_func_lib} INTERFACE + "${CMAKE_BINARY_DIR}/ppc_json/install/include") +endfunction() \ No newline at end of file diff --git a/cmake/libenvpp.cmake b/cmake/libenvpp.cmake index 564a7d488..e150de19b 100644 --- a/cmake/libenvpp.cmake +++ b/cmake/libenvpp.cmake @@ -38,3 +38,21 @@ if(WIN32) else() set(PPC_ENVPP_LIB_NAME envpp) endif() + +function(ppc_link_envpp exec_func_lib) + # Add external project include directories + target_include_directories( + ${exec_func_lib} + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/libenvpp/include) + target_include_directories( + ${exec_func_lib} SYSTEM + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/libenvpp/external/fmt/include) + + add_dependencies(${exec_func_lib} ppc_libenvpp) + target_link_directories(${exec_func_lib} PUBLIC + "${CMAKE_BINARY_DIR}/ppc_libenvpp/install/lib") + target_link_directories(${exec_func_lib} PUBLIC + "${CMAKE_BINARY_DIR}/ppc_libenvpp/build") + target_link_libraries(${exec_func_lib} PUBLIC ${PPC_ENVPP_LIB_NAME}) + target_link_libraries(${exec_func_lib} PUBLIC ${PPC_FMT_LIB_NAME}) +endfunction() diff --git a/cmake/mpi.cmake b/cmake/mpi.cmake index 8b307ccdd..9394ff932 100644 --- a/cmake/mpi.cmake +++ b/cmake/mpi.cmake @@ -1,4 +1,19 @@ find_package(MPI REQUIRED) if(NOT MPI_FOUND) message(FATAL_ERROR "MPI NOT FOUND") -endif(MPI_FOUND) +endif() + +function(ppc_link_mpi exec_func_lib) + find_package(MPI REQUIRED) + if(MPI_COMPILE_FLAGS) + set_target_properties(${exec_func_lib} PROPERTIES COMPILE_FLAGS + "${MPI_COMPILE_FLAGS}") + endif(MPI_COMPILE_FLAGS) + + if(MPI_LINK_FLAGS) + set_target_properties(${exec_func_lib} PROPERTIES LINK_FLAGS + "${MPI_LINK_FLAGS}") + endif(MPI_LINK_FLAGS) + target_include_directories(${exec_func_lib} PUBLIC ${MPI_INCLUDE_PATH}) + target_link_libraries(${exec_func_lib} PUBLIC ${MPI_LIBRARIES}) +endfunction() diff --git a/cmake/onetbb.cmake b/cmake/onetbb.cmake index df89aa354..b14b2ed0e 100644 --- a/cmake/onetbb.cmake +++ b/cmake/onetbb.cmake @@ -42,3 +42,17 @@ if(cmake_build_type_lower STREQUAL "debug") else() set(PPC_TBB_LIB_NAME tbb) endif() + +function(ppc_link_tbb exec_func_lib) + # Add external project include directories + target_include_directories( + ${exec_func_lib} + PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/onetbb/include) + + add_dependencies(${exec_func_lib} ppc_onetbb) + target_link_directories(${exec_func_lib} PUBLIC + ${CMAKE_BINARY_DIR}/ppc_onetbb/install/lib) + if(NOT MSVC) + target_link_libraries(${exec_func_lib} PUBLIC ${PPC_TBB_LIB_NAME}) + endif() +endfunction() diff --git a/cmake/openmp.cmake b/cmake/openmp.cmake index 445815153..33b56e339 100644 --- a/cmake/openmp.cmake +++ b/cmake/openmp.cmake @@ -23,3 +23,13 @@ if(OpenMP_FOUND) else(OpenMP_FOUND) message(FATAL_ERROR "OpenMP NOT FOUND") endif(OpenMP_FOUND) + +function(ppc_link_threads exec_func_lib) + target_link_libraries(${exec_func_lib} PUBLIC Threads::Threads) +endfunction() + +function(ppc_link_openmp exec_func_lib) + find_package(OpenMP REQUIRED) + target_link_libraries(${exec_func_lib} PUBLIC ${OpenMP_libomp_LIBRARY} + OpenMP::OpenMP_CXX) +endfunction() diff --git a/cmake/stb.cmake b/cmake/stb.cmake new file mode 100644 index 000000000..2770d4440 --- /dev/null +++ b/cmake/stb.cmake @@ -0,0 +1,5 @@ +function(ppc_link_stb exec_func_lib) + add_library(stb_image STATIC ${CMAKE_SOURCE_DIR}/3rdparty/stb_image_wrapper.cpp) + target_include_directories(stb_image PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/stb) + target_link_libraries(${exec_func_lib} PUBLIC stb_image) +endfunction() \ No newline at end of file diff --git a/modules/core/CMakeLists.txt b/modules/core/CMakeLists.txt index 318572711..487b2c9f5 100644 --- a/modules/core/CMakeLists.txt +++ b/modules/core/CMakeLists.txt @@ -29,63 +29,14 @@ target_include_directories( ${exec_func_lib} PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty ${CMAKE_SOURCE_DIR}/modules ${CMAKE_SOURCE_DIR}/tasks) -# Add external project include directories -target_include_directories( - ${exec_func_lib} - PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/onetbb/include - ${CMAKE_SOURCE_DIR}/3rdparty/json/include - ${CMAKE_SOURCE_DIR}/3rdparty/googletest/googletest/include - ${CMAKE_SOURCE_DIR}/3rdparty/libenvpp/include) -target_include_directories( - ${exec_func_lib} SYSTEM - PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/libenvpp/external/fmt/include) - -add_dependencies(${exec_func_lib} ppc_libenvpp) -target_link_directories(${exec_func_lib} PUBLIC - "${CMAKE_BINARY_DIR}/ppc_libenvpp/install/lib") -target_link_directories(${exec_func_lib} PUBLIC - "${CMAKE_BINARY_DIR}/ppc_libenvpp/build") -target_link_libraries(${exec_func_lib} PUBLIC ${PPC_ENVPP_LIB_NAME}) -target_link_libraries(${exec_func_lib} PUBLIC ${PPC_FMT_LIB_NAME}) - -add_dependencies(${exec_func_lib} ppc_json) -target_link_directories(${exec_func_lib} INTERFACE - "${CMAKE_BINARY_DIR}/ppc_json/install/include") - -add_dependencies(${exec_func_lib} ppc_googletest) -target_link_directories(${exec_func_lib} PUBLIC - "${CMAKE_BINARY_DIR}/ppc_googletest/install/lib") -target_link_libraries(${exec_func_lib} PUBLIC gtest gtest_main) - -target_link_libraries(${exec_func_lib} PUBLIC Threads::Threads) - -find_package(OpenMP REQUIRED) -target_link_libraries(${exec_func_lib} PUBLIC ${OpenMP_libomp_LIBRARY} - OpenMP::OpenMP_CXX) - -add_dependencies(${exec_func_lib} ppc_onetbb) -target_link_directories(${exec_func_lib} PUBLIC - ${CMAKE_BINARY_DIR}/ppc_onetbb/install/lib) -if(NOT MSVC) - target_link_libraries(${exec_func_lib} PUBLIC ${PPC_TBB_LIB_NAME}) -endif() - -find_package(MPI REQUIRED) -if(MPI_COMPILE_FLAGS) - set_target_properties(${exec_func_lib} PROPERTIES COMPILE_FLAGS - "${MPI_COMPILE_FLAGS}") -endif(MPI_COMPILE_FLAGS) - -if(MPI_LINK_FLAGS) - set_target_properties(${exec_func_lib} PROPERTIES LINK_FLAGS - "${MPI_LINK_FLAGS}") -endif(MPI_LINK_FLAGS) -target_include_directories(${exec_func_lib} PUBLIC ${MPI_INCLUDE_PATH}) -target_link_libraries(${exec_func_lib} PUBLIC ${MPI_LIBRARIES}) - -add_library(stb_image STATIC ${CMAKE_SOURCE_DIR}/3rdparty/stb_image_wrapper.cpp) -target_include_directories(stb_image PUBLIC ${CMAKE_SOURCE_DIR}/3rdparty/stb) -target_link_libraries(${exec_func_lib} PUBLIC stb_image) +ppc_link_envpp(${exec_func_lib}) +ppc_link_json(${exec_func_lib}) +ppc_link_gtest(${exec_func_lib}) +ppc_link_threads(${exec_func_lib}) +ppc_link_openmp(${exec_func_lib}) +ppc_link_tbb(${exec_func_lib}) +ppc_link_mpi(${exec_func_lib}) +ppc_link_stb(${exec_func_lib}) add_executable(${exec_func_tests} ${FUNC_TESTS_SOURCE_FILES}) From dcb06876d9d8ef12b1e8dfedd74c3746cdfa2857 Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Thu, 3 Jul 2025 18:39:39 +0200 Subject: [PATCH 2/4] enhance performance module: improve code documentation, refactor performance measurement logic, and add utility for result string conversion --- modules/performance/include/performance.hpp | 209 ++++++++++++++------ 1 file changed, 147 insertions(+), 62 deletions(-) diff --git a/modules/performance/include/performance.hpp b/modules/performance/include/performance.hpp index eaafc5378..e14da7407 100644 --- a/modules/performance/include/performance.hpp +++ b/modules/performance/include/performance.hpp @@ -11,112 +11,197 @@ #include "task/include/task.hpp" +using namespace ppc::task; + namespace ppc::performance { +/** + * @brief Default timer function used for performance measurement. + * @return A fake time value (-1.0). + */ inline double DefaultTimer() { return -1.0; } +/** + * @brief Attributes used to configure performance measurement. + */ struct PerfAttr { - /// @brief Number of times the task is run for performance evaluation. + /** + * @brief Number of repetitions of the task for averaging performance. + */ uint64_t num_running = 5; - /// @brief Timer function returning current time in seconds. - /// @cond + + /** + * @brief Timer function returning current time in seconds. + * Default is a fake function returning -1.0. + */ std::function current_timer = DefaultTimer; - /// @endcond }; +/** + * @brief Stores the results of a performance test. + */ struct PerfResults { - /// @brief Measured execution time in seconds. + /** + * @brief Measured execution time in seconds (average over runs). + */ double time_sec = 0.0; - enum TypeOfRunning : uint8_t { kPipeline, kTaskRun, kNone } type_of_running = kNone; + + /** + * @brief Type of performance test performed. + */ + enum TypeOfRunning : uint8_t { + kPipeline, ///< Full pipeline: Validation → PreProcessing → Run → PostProcessing + kTaskRun, ///< Only Run() function is measured + kNone ///< No performance test was executed + } type_of_running = kNone; + + /** + * @brief Maximum allowed execution time in seconds. + */ constexpr static double kMaxTime = 10.0; }; +/** + * @brief Converts a TypeOfRunning enum value to its string representation. + * @param type_of_running Enum value indicating which performance type was run. + * @return String name corresponding to the enum. + */ +inline std::string GetStringParamName(PerfResults::TypeOfRunning type_of_running) { + switch (type_of_running) { + case PerfResults::kTaskRun: + return "task_run"; + case PerfResults::kPipeline: + return "pipeline"; + default: + return "none"; + } +} + +/** + * @brief Measures performance of a given task using configured attributes. + * @tparam InType Input type of the task. + * @tparam OutType Output type of the task. + */ template class Perf { public: - // Init performance analysis with an initialized task and initialized data - explicit Perf(const ppc::task::TaskPtr& task_ptr) : task_(task_ptr) { - task_ptr->GetStateOfTesting() = ppc::task::StateOfTesting::kPerf; + /** + * @brief Constructs a performance tester for the given task. + * @param task_ptr Shared pointer to a task object. + */ + explicit Perf(const ::TaskPtr& task_ptr) : task_(task_ptr) { + task_ptr->GetStateOfTesting() = ::StateOfTesting::kPerf; } - // Check performance of full task's pipeline: PreProcessing() -> - // Validation() -> Run() -> PostProcessing() + + /** + * @brief Runs the full task pipeline and measures its performance. + * The full pipeline includes: Validation → PreProcessing → Run → PostProcessing. + * @param perf_attr Performance measurement configuration. + */ void PipelineRun(const PerfAttr& perf_attr) { - perf_results_.type_of_running = PerfResults::TypeOfRunning::kPipeline; - - CommonRun(perf_attr, [&] { - task_->Validation(); - task_->PreProcessing(); - task_->Run(); - task_->PostProcessing(); - }, perf_results_); + perf_results_.type_of_running = PerfResults::kPipeline; + CommonRun(perf_attr, RunFullPipeline<::Task>); } - // Check performance of task's Run() function + + /** + * @brief Measures only the Run() part of the task. + * Pre/Post-processing and validation are still invoked before and after. + * @param perf_attr Performance measurement configuration. + */ void TaskRun(const PerfAttr& perf_attr) { - perf_results_.type_of_running = PerfResults::TypeOfRunning::kTaskRun; + perf_results_.type_of_running = PerfResults::kTaskRun; task_->Validation(); task_->PreProcessing(); - CommonRun(perf_attr, [&] { task_->Run(); }, perf_results_); + CommonRun(perf_attr, RunOnly<::Task>); task_->PostProcessing(); + // Ensure correctness after performance run task_->Validation(); task_->PreProcessing(); task_->Run(); task_->PostProcessing(); } - // Pint results for automation checkers + + /** + * @brief Prints formatted performance results or throws if too slow. + * Prints output in format: test_id:type:time_in_seconds + * @param test_id Identifier for the current test (e.g., "omp_4_threads"). + * @throws std::runtime_error if execution time exceeds allowed maximum. + */ void PrintPerfStatistic(const std::string& test_id) const { - std::string type_test_name; - if (perf_results_.type_of_running == PerfResults::TypeOfRunning::kTaskRun) { - type_test_name = "task_run"; - } else if (perf_results_.type_of_running == PerfResults::TypeOfRunning::kPipeline) { - type_test_name = "pipeline"; - } else { - std::stringstream err_msg; - err_msg << '\n' << "The type of performance check for the task was not selected.\n"; - throw std::runtime_error(err_msg.str().c_str()); + const auto& type = perf_results_.type_of_running; + const std::string type_name = GetStringParamName(type); + + if (type == PerfResults::kNone) { + throw std::runtime_error("The type of performance check for the task was not selected.\n"); } - auto time_secs = perf_results_.time_sec; - std::stringstream perf_res_str; + std::stringstream ss; + double time_secs = perf_results_.time_sec; + if (time_secs < PerfResults::kMaxTime) { - perf_res_str << std::fixed << std::setprecision(10) << time_secs; - std::cout << test_id << ":" << type_test_name << ":" << perf_res_str.str() << '\n'; + ss << std::fixed << std::setprecision(10) << time_secs; } else { - std::stringstream err_msg; - err_msg << '\n' << "Task execute time need to be: "; - err_msg << "time < " << PerfResults::kMaxTime << " secs." << '\n'; - err_msg << "Original time in secs: " << time_secs << '\n'; - perf_res_str << std::fixed << std::setprecision(10) << -1.0; - std::cout << test_id << ":" << type_test_name << ":" << perf_res_str.str() << '\n'; - throw std::runtime_error(err_msg.str().c_str()); + ss << std::fixed << std::setprecision(10) << -1.0; + std::stringstream err; + err << "Task execute time need to be: time < " << PerfResults::kMaxTime + << " secs.\nOriginal time in secs: " << time_secs << '\n'; + std::cout << test_id << ":" << type_name << ":" << ss.str() << '\n'; + throw std::runtime_error(err.str()); } + + std::cout << test_id << ":" << type_name << ":" << ss.str() << '\n'; } - /// @brief Retrieves the performance test results. - /// @return The latest PerfResults structure. + + /** + * @brief Retrieves performance test results. + * @return Struct containing latest performance data. + */ [[nodiscard]] PerfResults GetPerfResults() const { return perf_results_; } private: - PerfResults perf_results_; - std::shared_ptr> task_; - static void CommonRun(const PerfAttr& perf_attr, const std::function& pipeline, PerfResults& perf_results) { - auto begin = perf_attr.current_timer(); - for (uint64_t i = 0; i < perf_attr.num_running; i++) { - pipeline(); - } - auto end = perf_attr.current_timer(); - perf_results.time_sec = (end - begin) / static_cast(perf_attr.num_running); + PerfResults perf_results_; ///< Stores measurement results and mode. + std::shared_ptr<::Task> task_; ///< Pointer to task being tested. + + /** + * @brief Executes the full pipeline for the given task. + * @tparam TaskType Type of the task. + * @param task Shared pointer to the task instance. + */ + template + static void RunFullPipeline(const std::shared_ptr& task) { + task->Validation(); + task->PreProcessing(); + task->Run(); + task->PostProcessing(); } -}; -inline std::string GetStringParamName(PerfResults::TypeOfRunning type_of_running) { - if (type_of_running == PerfResults::kTaskRun) { - return "task_run"; + /** + * @brief Executes only the Run() method of the given task. + * @tparam TaskType Type of the task. + * @param task Shared pointer to the task instance. + */ + template + static void RunOnly(const std::shared_ptr& task) { + task->Run(); } - if (type_of_running == PerfResults::kPipeline) { - return "pipeline"; + + /** + * @brief Measures execution time of a given function over multiple runs. + * @tparam Func Type of callable taking shared_ptr to task. + * @param perf_attr Attributes controlling the number of runs and timer. + * @param func Callable that invokes the desired part of the task. + */ + template + void CommonRun(const PerfAttr& perf_attr, Func func) { + double begin = perf_attr.current_timer(); + for (uint64_t i = 0; i < perf_attr.num_running; ++i) { + func(task_); + } + double end = perf_attr.current_timer(); + perf_results_.time_sec = (end - begin) / static_cast(perf_attr.num_running); } - return "none"; -} +}; } // namespace ppc::performance From 85390bea47fe8ece5b13133f190c7407578a21f1 Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Thu, 3 Jul 2025 18:40:03 +0200 Subject: [PATCH 3/4] comment out unnecessary dependencies in ubuntu workflow --- .github/workflows/ubuntu.yml | 6 +++--- modules/performance/include/performance.hpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 493dc482b..cab068bad 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -332,9 +332,9 @@ jobs: PPC_NUM_PROC: 1 PPC_ASAN_RUN: 1 gcc-build-codecov: - needs: - - gcc-test-extended - - clang-test-extended +# needs: +# - gcc-test-extended +# - clang-test-extended runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/modules/performance/include/performance.hpp b/modules/performance/include/performance.hpp index e14da7407..c398a042c 100644 --- a/modules/performance/include/performance.hpp +++ b/modules/performance/include/performance.hpp @@ -105,7 +105,7 @@ class Perf { /** * @brief Measures only the Run() part of the task. - * Pre/Post-processing and validation are still invoked before and after. + * Pre- / Post-processing and validation are still invoked before and after. * @param perf_attr Performance measurement configuration. */ void TaskRun(const PerfAttr& perf_attr) { @@ -116,7 +116,7 @@ class Perf { CommonRun(perf_attr, RunOnly<::Task>); task_->PostProcessing(); - // Ensure correctness after performance run + // Ensure correctness after a performance run task_->Validation(); task_->PreProcessing(); task_->Run(); From 203af6970f1cfa02e6aafcc1364587902971f5c2 Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Thu, 3 Jul 2025 20:24:33 +0200 Subject: [PATCH 4/4] simplify GetStringParamName implementation in performance module --- modules/performance/include/performance.hpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/performance/include/performance.hpp b/modules/performance/include/performance.hpp index c398a042c..3b185ef11 100644 --- a/modules/performance/include/performance.hpp +++ b/modules/performance/include/performance.hpp @@ -67,14 +67,13 @@ struct PerfResults { * @return String name corresponding to the enum. */ inline std::string GetStringParamName(PerfResults::TypeOfRunning type_of_running) { - switch (type_of_running) { - case PerfResults::kTaskRun: - return "task_run"; - case PerfResults::kPipeline: - return "pipeline"; - default: - return "none"; + if (type_of_running == PerfResults::kTaskRun) { + return "task_run"; } + if (type_of_running == PerfResults::kPipeline) { + return "pipeline"; + } + return "none"; } /**