diff --git a/CMakeLists.txt b/CMakeLists.txt index f54c5b3f8c07..d14134747b3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,9 @@ endif() # If no test policy enable by default option(KRATOS_BUILD_TESTING "KRATOS_BUILD_TESTING defines if the C++ tests are compiled. These increase compilation time, but if not set only python tests can be executed. Default setting is ON" ON) +# If no benchmark policy enable by default +option(KRATOS_BUILD_BENCHMARK "KRATOS_BUILD_BENCHMARK defines if the C++ benchmarks (Google benchmark) are compiled. These increase compilation time. Default setting is OFF" OFF) + # If no pch policy disable by default option(KRATOS_USE_PCH "KRATOS_USE_PCH defines if pch will be used during the compilation. This may will decrease compilation for clean build or if core components are touched. Default setting is OFF" OFF) @@ -288,6 +291,7 @@ SET(INSTALL_PYTHON_FILES ON) # To be removed when all applications are po include(install_function) include(KratosDependencies) include(KratosGTest) +include(KratosGBenchmark) include(FetchContent) # Logger configuration @@ -320,6 +324,31 @@ if(KRATOS_BUILD_TESTING MATCHES ON) set_directory_properties(PROPERTIES COMPILE_OPTIONS "${kratos_root_compile_options}") endif(KRATOS_BUILD_TESTING MATCHES ON) +# Benchmarking +if(KRATOS_BUILD_BENCHMARK MATCHES ON) + # Add the definitions if required + ADD_DEFINITIONS(-DKRATOS_BUILD_BENCHMARKING) + + # Retrieve a copy of the current directory's `COMPILE_OPTIONS` + get_directory_property(kratos_root_compile_options COMPILE_OPTIONS) + + # Disable warnings (needed by centos. We should all love centos, it clearly needs some affection) + add_compile_options(-w) + + FetchContent_Declare( + googlebenchmark + URL https://github.com/google/benchmark/archive/v1.9.0.zip + ) + # For Windows: Prevent overriding the parent project's compiler/linker settings + set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) + set(CMAKE_INSTALL_LIBDIR "libs") # In Linux default dir is lib + set(CMAKE_INSTALL_BINDIR "libs") # In Win default dir is bin + FetchContent_MakeAvailable(googlebenchmark) + + # Restore the current directory's old `COMPILE_OPTIONS` + set_directory_properties(PROPERTIES COMPILE_OPTIONS "${kratos_root_compile_options}") +endif(KRATOS_BUILD_BENCHMARK MATCHES ON) + ################### PYBIND11 # Try to use python executable from env variable diff --git a/cmake_modules/KratosGBenchmark.cmake b/cmake_modules/KratosGBenchmark.cmake new file mode 100644 index 000000000000..1f51efaf4fbb --- /dev/null +++ b/cmake_modules/KratosGBenchmark.cmake @@ -0,0 +1,13 @@ +# This function automatically configures a given application to build its benchmarks +macro(kratos_add_benchmarks) + set(options USE_MPI USE_CUSTOM_MAIN) + set(oneValueArgs TARGET WORKING_DIRECTORY) + set(multiValueArgs SOURCES) + + cmake_parse_arguments(KRATOS_ADD_BENCHMARK "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(KRATOS_ADD_BENCHMARK_SOURCES) + include(GoogleBenchmark) + endif(KRATOS_ADD_BENCHMARK_SOURCES) + +endmacro(kratos_add_benchmarks) \ No newline at end of file diff --git a/kratos/CMakeLists.txt b/kratos/CMakeLists.txt index 825fb49776b4..282ba4101292 100644 --- a/kratos/CMakeLists.txt +++ b/kratos/CMakeLists.txt @@ -87,6 +87,21 @@ if(${KRATOS_BUILD_TESTING} MATCHES ON) gtest_discover_tests(KratosCoreTest DISCOVERY_MODE PRE_TEST) endif(${KRATOS_BUILD_TESTING} MATCHES ON) +## Kratos benchmark sources. Disabled by default +if(${KRATOS_BUILD_BENCHMARK} MATCHES ON) + file(GLOB_RECURSE KRATOS_BENCHMARK_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/benchmarks/*.cpp + ) + + foreach(file ${KRATOS_BENCHMARK_SOURCES}) + get_filename_component(filename ${file} NAME_WE) + add_executable(${filename} ${file}) + target_link_libraries(${filename} PUBLIC KratosCore benchmark::benchmark) + set_target_properties(${filename} PROPERTIES COMPILE_DEFINITIONS "KRATOS_BENCHMARK=IMPORT,API") + install(TARGETS ${filename} DESTINATION benchmark) + endforeach(file ${KRATOS_BENCHMARK_SOURCES}) +endif(${KRATOS_BUILD_BENCHMARK} MATCHES ON) + ## Define KratosVersion object add_library(KratosVersion OBJECT ${KRATOS_VERSION_SOURCES}) set_target_properties(KratosVersion PROPERTIES COMPILE_DEFINITIONS "KRATOS_VERSION=IMPORT,API") diff --git a/kratos/benchmarks/example_benchmark.cpp b/kratos/benchmarks/example_benchmark.cpp new file mode 100644 index 000000000000..ae2d2ad07bc8 --- /dev/null +++ b/kratos/benchmarks/example_benchmark.cpp @@ -0,0 +1,46 @@ +// | / | +// ' / __| _` | __| _ \ __| +// . \ | ( | | ( |\__ ` +// _|\_\_| \__,_|\__|\___/ ____/ +// Multi-Physics +// +// License: BSD License +// Kratos default license: kratos/license.txt +// +// Main authors: Vicente Mataix Ferrandiz +// + +// System includes + +// External includes +#include + +// Project includes +#include "geometries/point.h" +#include "geometries/triangle_3d_3.h" +#include "utilities/geometry_utilities/nearest_point_utilities.h" + +namespace Kratos +{ + +// Sample data for benchmarking +Point::Pointer p_point_1(make_shared( 0.0, 0.0, 0.0)); +Point::Pointer p_point_2(make_shared( 1.0, 0.0, 0.0)); +Point::Pointer p_point_3(make_shared( 0.0, 1.0, 0.0)); + +Triangle3D3 triangle(p_point_1, p_point_3, p_point_2); +Point nearest_point(0.0, 0.0, 0.0); +Point inside_point(0.2, 0.1, 0.00); + +static void BM_TriangleNearestPoint(benchmark::State& state) { + for (auto _ : state) { + NearestPointUtilities::TriangleNearestPoint(inside_point, triangle, nearest_point); + } +} + +// Register the function as a benchmark +BENCHMARK(BM_TriangleNearestPoint); + +} // namespace Kratos + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/kratos/python_scripts/benchmark/generate_plot_google_benchmark.py b/kratos/python_scripts/benchmark/generate_plot_google_benchmark.py new file mode 100644 index 000000000000..b7744cff62d4 --- /dev/null +++ b/kratos/python_scripts/benchmark/generate_plot_google_benchmark.py @@ -0,0 +1,89 @@ +import json +import pandas as pd +import matplotlib.pyplot as plt +import argparse + +def main(filenames): + """ + Main function to generate a combined plot from multiple Google Benchmark JSON outputs. + + Args: + filenames (list of str): List of paths to the JSON files containing benchmark results. + + The function performs the following steps: + 1. Loads the JSON files specified in the filenames list. + 2. Combines the 'benchmarks' section of all JSON data into a single pandas DataFrame. + 3. Plots the 'cpu_time' and 'real_time' for each benchmark with bars grouped by input filename. + + The resulting plot displays: + - X-axis: Benchmark names (with labels based on input filenames) + - Y-axis: Time in nanoseconds + - Bars grouped by CPU Time and Real Time for each file + + NOTE: The JSON files must be generated using Google Benchmark with: + ./benchmark_name --benchmark_format=json --benchmark_out=filename.json + """ + combined_data = [] + + # Process each JSON file + for filename in filenames: + with open(filename) as f: + data = json.load(f) + + # Extract benchmark information and add filename as a source + benchmarks = data["benchmarks"] + for benchmark in benchmarks: + benchmark["source"] = filename + combined_data.extend(benchmarks) + + # Convert combined data to a pandas DataFrame + benchmark_df = pd.DataFrame(combined_data) + + # Plot the results + plt.figure(figsize=(12, 8)) + bar_width = 0.35 + x_labels = benchmark_df["name"].unique() + x = range(len(x_labels)) + offset = 0 + + # Plot for each input file + for filename in filenames: + subset = benchmark_df[benchmark_df["source"] == filename] + plt.bar( + [pos + offset for pos in x], + subset["real_time"], + bar_width, + label=f"{filename} - Real Time", + alpha=0.7 + ) + plt.bar( + [pos + offset for pos in x], + subset["cpu_time"], + bar_width, + label=f"{filename} - CPU Time", + alpha=0.7 + ) + offset += bar_width + + # Customize the plot + plt.title("Benchmark Performance Across Files") + plt.xlabel("Benchmark Name") + plt.ylabel("Time (ns)") + plt.xticks([pos + bar_width for pos in x], x_labels, rotation=45, ha="right") + plt.legend() + plt.tight_layout() + + # Show the plot + plt.show() + +if __name__ == "__main__": + """ + This script can be run from the command line to generate a combined plot from multiple Google Benchmark JSON outputs. + Example usage: + python generate_plot_google_benchmark.py --filenames filename1.json filename2.json + """ + # Parse command line arguments + parser = argparse.ArgumentParser(description='Generate a combined plot from multiple Google Benchmark JSON results.') + parser.add_argument('--filenames', nargs='+', type=str, help='The list of JSON files containing benchmark results') + args = parser.parse_args() + main(args.filenames) \ No newline at end of file