From 3496151195c43c32a6349479aa711efba9408243 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Thu, 3 Sep 2026 16:50:58 +0000 Subject: [PATCH 1/5] Check all Python benchmark dependencies at configure time Previously only pytest itself was detected at configure time. If pytest-benchmark or pytest-csv were missing, the Python benchmarks were still registered as tests and then failed at runtime, because the pytest invocation uses the --csv option. Now all required modules are checked at configure time; if any is missing, a warning lists the missing modules and invites to install them from requirements.txt, and the Python benchmarks are skipped. Closes #123, closes #150. --- cmake/modules/AddRootBench.cmake | 4 ++-- cmake/modules/PytestBenchmark.cmake | 21 ++++++++++++++++----- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/cmake/modules/AddRootBench.cmake b/cmake/modules/AddRootBench.cmake index 1661f474..dc0a8839 100644 --- a/cmake/modules/AddRootBench.cmake +++ b/cmake/modules/AddRootBench.cmake @@ -101,7 +101,7 @@ function(RB_ADD_PYTESTBENCHMARK benchmark) endif() set(filename ${ARG_UNPARSED_ARGUMENTS}) - if(PYTEST_FOUND) + if(RB_PYTEST_FOUND) if(ARG_DOWNLOAD_DATAFILES) RB_ADD_DOWNLOAD_FIXTURE(${benchmark} DOWNLOAD_DATAFILES ${ARG_DOWNLOAD_DATAFILES}) endif() @@ -119,7 +119,7 @@ function(RB_ADD_PYTESTBENCHMARK benchmark) TIMEOUT "${TIMEOUT_VALUE}" LABELS "${ARG_LABEL}" RUN_SERIAL TRUE FIXTURES_REQUIRED "setup-${benchmark};download-${benchmark}-datafiles") else() - message(STATUS "pytest was not found, benchmark " ${benchmark} " will be ignored") + message(STATUS "Python modules required for pytest benchmarks were not found, benchmark " ${benchmark} " will be ignored") endif() else() diff --git a/cmake/modules/PytestBenchmark.cmake b/cmake/modules/PytestBenchmark.cmake index ed0cbd18..49d7472d 100644 --- a/cmake/modules/PytestBenchmark.cmake +++ b/cmake/modules/PytestBenchmark.cmake @@ -25,10 +25,21 @@ if(NOT (CMAKE_PROJECT_NAME STREQUAL ROOT)) endif() endif() -# Find mandatory dependency (for Python benchmarks), but quietly :) -find_python_module(pytest QUIET) +# Find mandatory dependencies (for Python benchmarks), but quietly :) +set(RB_PYTEST_MISSING_MODULES) +foreach(module pytest pytest_benchmark pytest_csv) + find_python_module(${module} QUIET) + string(TOUPPER ${module} module_upper) + if(NOT ${module_upper}_FOUND) + list(APPEND RB_PYTEST_MISSING_MODULES ${module}) + endif() +endforeach() -if(NOT PYTEST_FOUND) -message(STATUS "Be aware that pytest benchmarks are not enabled (missing pytest & pytest-benchmark dependency). - Please install them using pip and provided requirements.txt") +if(RB_PYTEST_MISSING_MODULES) + set(RB_PYTEST_FOUND FALSE CACHE INTERNAL "" FORCE) + string(REPLACE ";" ", " missing_modules "${RB_PYTEST_MISSING_MODULES}") + message(WARNING "Python benchmarks are not enabled: ${PYTHON_EXECUTABLE} is missing the module(s) ${missing_modules}. + Please install them using pip and the provided requirements.txt") +else() + set(RB_PYTEST_FOUND TRUE CACHE INTERNAL "" FORCE) endif() From 1d0c0063a0d7fb77b9b83179ba9fc0341bb653f6 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Thu, 3 Sep 2026 16:50:58 +0000 Subject: [PATCH 2/5] Make gen_h1 a no-op if the output file already exists This speeds up repeated executions of the RNTuple tests considerably, since the H1 dst conversion is skipped when the output ntuple is already there. Delete the output files to force regeneration. Closes #132. --- root/tree/tree/gen_h1.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/root/tree/tree/gen_h1.cxx b/root/tree/tree/gen_h1.cxx index a31d301e..8e3345a5 100644 --- a/root/tree/tree/gen_h1.cxx +++ b/root/tree/tree/gen_h1.cxx @@ -98,6 +98,10 @@ int main(int argc, char **argv) outputFile += std::string("X") + ((bloatFactor < 10) ? "0" : "") + std::to_string(bloatFactor); } outputFile += std::string("-") + compressionShorthand + ".ntuple"; + if (!gSystem->AccessPathName(outputFile.c_str())) { + std::cout << "Output file " << outputFile << " already exists, nothing to do" << std::endl; + return 0; + } std::cout << "Converting " << JoinStrings(inputFiles, " ") << " --> " << outputFile << std::endl; TChain *tree = new TChain("h42"); From 012c47a2412ce1f38c2ff72b104709638f8d7eb3 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Thu, 3 Sep 2026 16:50:58 +0000 Subject: [PATCH 3/5] Document the RB_TEMP_FS environment variable in the README Closes #149. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 55d7bf62..669a3f70 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,13 @@ also, you can use cmake --build . -- -jN ``` where 'N' is the maximum number of processor cores you want to use. + +### Runtime environment variables +Some benchmarks write temporary files while they run. The directory used for these files can be chosen with the `RB_TEMP_FS` environment variable before invoking the benchmarks (e.g. via `ctest`): +```bash +export RB_TEMP_FS=/dev/shm +``` +Ideally this points to a RAM-backed filesystem such as `/dev/shm` on Linux, so that disk I/O does not distort the measurements. If the variable is not set, the current working directory is used and a notice is printed. ## Extending the benchmarks ROOTBench relies on [Google Benchmark](https://github.com/google/benchmark). We recommend to read the [available documentation](https://github.com/google/benchmark/blob/master/README.md) and browse the existing examples [here](https://github.com/google/benchmark/tree/master/test) for more advanced usage. From e82524a0147704c2eef5f3acd14cc73144dc3494 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Thu, 3 Sep 2026 16:50:58 +0000 Subject: [PATCH 4/5] Make sure hadd is available at configuration time hadd is used by the setup fixtures of several benchmarks. Locate it at configuration time and fail with a clear error if it cannot be found, instead of failing at test runtime. When building as part of ROOT, hadd from the same build tree is used, since it does not exist yet at configuration time. Closes #193. --- CMakeLists.txt | 12 ++++++++++++ root/tree/tree/CMakeLists.txt | 8 ++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 507d236b..8a5dd80a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,18 @@ if(NOT DEFINED ROOTSYS) endif() get_filename_component(ROOT_LIBRARY_DIR "${ROOTSYS}/lib" ABSOLUTE) +#---Several benchmarks rely on hadd in their setup fixtures, so make sure it is available. +if(CMAKE_PROJECT_NAME STREQUAL ROOT) + # When building as part of ROOT, hadd is built in the same build tree and + # does not exist yet at configuration time. + set(RB_HADD_EXECUTABLE ${CMAKE_BINARY_DIR}/bin/hadd) +else() + find_program(RB_HADD_EXECUTABLE hadd HINTS ${ROOTSYS}/bin) + if(NOT RB_HADD_EXECUTABLE) + message(FATAL_ERROR "hadd was not found, neither in PATH nor in ${ROOTSYS}/bin. It is required by several benchmarks, please make sure it is provided by your ROOT installation.") + endif() +endif() + #---Define useful ROOT functions and macros (e.g. ROOT_GENERATE_DICTIONARY) include(${ROOT_USE_FILE}) diff --git a/root/tree/tree/CMakeLists.txt b/root/tree/tree/CMakeLists.txt index 7dcf434f..2faa0023 100644 --- a/root/tree/tree/CMakeLists.txt +++ b/root/tree/tree/CMakeLists.txt @@ -26,10 +26,10 @@ if(ROOT_root7_FOUND AND rootbench-datafiles AND ROOT_dataframe_FOUND) SETUP "${CMAKE_CURRENT_BINARY_DIR}/gen_h1 -o ${RB_DATASETDIR} -c lz4 ${RB_DATASETDIR}/h1dst-lzma.root" SETUP "${CMAKE_CURRENT_BINARY_DIR}/gen_h1 -o ${RB_DATASETDIR} -c zstd ${RB_DATASETDIR}/h1dst-lzma.root" SETUP "${CMAKE_CURRENT_BINARY_DIR}/gen_h1 -o ${RB_DATASETDIR} -c none ${RB_DATASETDIR}/h1dst-lzma.root" - SETUP "hadd -f101 ${RB_DATASETDIR}/h1dst-zlib.root ${RB_DATASETDIR}/h1dst-lzma.root" - SETUP "hadd -f404 ${RB_DATASETDIR}/h1dst-lz4.root ${RB_DATASETDIR}/h1dst-lzma.root" - SETUP "hadd -f506 ${RB_DATASETDIR}/h1dst-zstd.root ${RB_DATASETDIR}/h1dst-lzma.root" - SETUP "hadd -f0 ${RB_DATASETDIR}/h1dst-none.root ${RB_DATASETDIR}/h1dst-lzma.root") + SETUP "${RB_HADD_EXECUTABLE} -f101 ${RB_DATASETDIR}/h1dst-zlib.root ${RB_DATASETDIR}/h1dst-lzma.root" + SETUP "${RB_HADD_EXECUTABLE} -f404 ${RB_DATASETDIR}/h1dst-lz4.root ${RB_DATASETDIR}/h1dst-lzma.root" + SETUP "${RB_HADD_EXECUTABLE} -f506 ${RB_DATASETDIR}/h1dst-zstd.root ${RB_DATASETDIR}/h1dst-lzma.root" + SETUP "${RB_HADD_EXECUTABLE} -f0 ${RB_DATASETDIR}/h1dst-none.root ${RB_DATASETDIR}/h1dst-lzma.root") endif(ROOT_root7_FOUND AND rootbench-datafiles AND ROOT_dataframe_FOUND) if(ROOT_root7_FOUND AND rootbench-datafiles) From a0b6e8189ce292b040c188aeebeda3befc04a2d2 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Thu, 3 Sep 2026 16:50:41 +0000 Subject: [PATCH 5/5] Adapt to RNTuple classes moving out of ROOT::Experimental The RNTuple classes graduated from the ROOT::Experimental namespace to the ROOT namespace, so the code didn't compile anymore against recent ROOT versions. --- root/tree/dataframe/RNTupleDSBenchmarks.cxx | 2 +- root/tree/tree/RNTupleH1Benchmarks.cxx | 4 ++-- root/tree/tree/RNTupleLHCBBenchmarks.cxx | 2 +- root/tree/tree/gen_h1.cxx | 7 +++---- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/root/tree/dataframe/RNTupleDSBenchmarks.cxx b/root/tree/dataframe/RNTupleDSBenchmarks.cxx index 057daaa4..09fc9014 100644 --- a/root/tree/dataframe/RNTupleDSBenchmarks.cxx +++ b/root/tree/dataframe/RNTupleDSBenchmarks.cxx @@ -58,7 +58,7 @@ auto Dataframe(DF &frame) static void BM_RNTupleDS_LHCB(benchmark::State &state) { - auto ntuple = ROOT::Experimental::RNTupleReader::Open("DecayTree", RB::GetDataDir() + "/B2HHH~none.rc2.ntuple"); + auto ntuple = ROOT::RNTupleReader::Open("DecayTree", RB::GetDataDir() + "/B2HHH~none.rc2.ntuple"); const Long64_t nEntries = ntuple->GetNEntries() * (state.range(0) / 100.); ROOT::RDataFrame df(RB::GetDataDir() + "/B2HHH~none.rc2.ntuple", "DecayTree"); diff --git a/root/tree/tree/RNTupleH1Benchmarks.cxx b/root/tree/tree/RNTupleH1Benchmarks.cxx index 565fe3b1..dd16e881 100644 --- a/root/tree/tree/RNTupleH1Benchmarks.cxx +++ b/root/tree/tree/RNTupleH1Benchmarks.cxx @@ -22,7 +22,7 @@ static void BM_RNTuple_H1(benchmark::State &state, const std::string &comprAlgorithm) { - using RNTupleReader = ROOT::Experimental::RNTupleReader; + using ROOT::RNTupleReader; gSystem->Load("./libh1event"); // Open RNtuple file with RNTuple reader std::string path = RB::GetDataDir() + "/h1dst-" + comprAlgorithm + ".ntuple"; @@ -46,7 +46,7 @@ static void BM_RNTuple_H1(benchmark::State &state, const std::string &comprAlgor auto njetsView = ntuple->GetCollectionView("event.jets"); // Check print info (minitest) std::ostringstream os; - ntuple->PrintInfo(ROOT::Experimental::ENTupleInfo::kSummary, os); + ntuple->PrintInfo(ROOT::ENTupleInfo::kSummary, os); // Benchmark loop for (auto _ : state) { // TH1D & TH2D histograms to be filled with data diff --git a/root/tree/tree/RNTupleLHCBBenchmarks.cxx b/root/tree/tree/RNTupleLHCBBenchmarks.cxx index 0e2e97eb..3b713b7c 100644 --- a/root/tree/tree/RNTupleLHCBBenchmarks.cxx +++ b/root/tree/tree/RNTupleLHCBBenchmarks.cxx @@ -20,7 +20,7 @@ double GetKE(double px, double py, double pz) static void BM_RNTuple_LHCB(benchmark::State &state) { - using RNTupleReader = ROOT::Experimental::RNTupleReader; + using ROOT::RNTupleReader; auto ntuple = RNTupleReader::Open("DecayTree", RB::GetDataDir() + "/B2HHH~none.rc2.ntuple"); auto viewH1IsMuon = ntuple->GetView("H1_isMuon"); diff --git a/root/tree/tree/gen_h1.cxx b/root/tree/tree/gen_h1.cxx index 8e3345a5..9e98602c 100644 --- a/root/tree/tree/gen_h1.cxx +++ b/root/tree/tree/gen_h1.cxx @@ -24,10 +24,9 @@ #include "h1event.h" -// Import classes from experimental namespace for the time being -using RNTupleModel = ROOT::Experimental::RNTupleModel; -using RNTupleWriter = ROOT::Experimental::RNTupleWriter; -using RNTupleWriteOptions = ROOT::Experimental::RNTupleWriteOptions; +using ROOT::RNTupleModel; +using ROOT::RNTupleWriter; +using ROOT::RNTupleWriteOptions; std::string JoinStrings(const std::vector &strings, const std::string &joint) {