openPMD-api
 
Loading...
Searching...
No Matches
MPIBenchmark.hpp
1/* Copyright 2018-2025 Franz Poeschel, Axel Huebl
2 *
3 * This file is part of openPMD-api.
4 *
5 * openPMD-api is free software: you can redistribute it and/or modify
6 * it under the terms of of either the GNU General Public License or
7 * the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * openPMD-api is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License and the GNU Lesser General Public License
15 * for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * and the GNU Lesser General Public License along with openPMD-api.
19 * If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#pragma once
23
24#include "openPMD/config.hpp"
25#if openPMD_HAVE_MPI
26
27#include "RandomDatasetFiller.hpp"
28
29#include "openPMD/DatatypeHelpers.hpp"
30#include "openPMD/benchmark/mpi/BlockSlicer.hpp"
31#include "openPMD/benchmark/mpi/DatasetFiller.hpp"
32#include "openPMD/benchmark/mpi/MPIBenchmarkReport.hpp"
33#include "openPMD/openPMD.hpp"
34
35#include <mpi.h>
36
37#include <chrono>
38#include <exception>
39#include <iostream>
40#include <set>
41#include <sstream>
42#include <tuple>
43#include <utility>
44#include <vector>
45
46namespace openPMD
47{
56template <typename DatasetFillerProvider>
58{
59
60public:
61 using extentT = Extent::value_type;
62 MPI_Comm communicator = MPI_COMM_WORLD;
63
68
69 std::shared_ptr<BlockSlicer> m_blockSlicer;
70
71 DatasetFillerProvider m_dfp;
72
88 std::string basePath,
89 Extent tExtent,
90 std::shared_ptr<BlockSlicer> blockSlicer,
91 DatasetFillerProvider dfp,
92 MPI_Comm comm = MPI_COMM_WORLD);
93
104 void addConfiguration(
105 std::string jsonConfig,
106 std::string backend,
107 Datatype dt,
108 Series::IterationIndex_t iterations,
109 int threadSize);
110
122 void addConfiguration(
123 std::string jsonConfig,
124 std::string backend,
125 Datatype dt,
126 Series::IterationIndex_t iterations);
127
128 void resetConfigurations();
129
138 template <typename Clock>
140 runBenchmark(int rootThread = 0);
141
142private:
143 std::string m_basePath;
144 std::vector<std::tuple<
145 std::string,
146 std::string,
147 int,
148 Datatype,
150 m_configurations;
151
152 enum Config
153 {
154 JSON_CONFIG = 0,
155 BACKEND,
156 NRANKS,
157 DTYPE,
158 ITERATIONS
159 };
160
161 std::pair<Offset, Extent> slice(int size);
162
169 template <typename Clock>
170 struct BenchmarkExecution
171 {
173
174 explicit BenchmarkExecution(
176 : m_benchmark{benchmark}
177 {}
178
190 template <typename T>
191 typename Clock::duration writeBenchmark(
192 std::string const &jsonConfig,
193 Offset &offset,
194 Extent &extent,
195 std::string const &extension,
196 std::shared_ptr<DatasetFiller<T>> datasetFiller,
197 Series::IterationIndex_t iterations);
198
208 template <typename T>
209 typename Clock::duration readBenchmark(
210 Offset &offset,
211 Extent &extent,
212 std::string extension,
213 Series::IterationIndex_t iterations);
214
215 template <typename T>
216 static void call(
217 BenchmarkExecution<Clock> &,
218 MPIBenchmarkReport<typename Clock::duration> &report,
219 int rootThread = 0);
220
221 static constexpr char const *errorMsg = "BenchmarkExecution";
222 };
223};
224
225// Implementation
226
227template <typename DatasetFillerProvider>
228template <typename Clock>
231{
232 MPIBenchmarkReport<typename Clock::duration> res{this->communicator};
233 BenchmarkExecution<Clock> exec{this};
234
235 std::set<Datatype> datatypes;
236 for (auto const &conf : m_configurations)
237 {
238 datatypes.insert(std::get<DTYPE>(conf));
239 }
240 for (Datatype dt : datatypes)
241 {
242 switchDatasetType<BenchmarkExecution<Clock>>(dt, exec, res, rootThread);
243 }
244
245 return res;
246}
247
248template <typename DatasetFillerProvider>
250 std::string basePath,
251 Extent tExtent,
252 std::shared_ptr<BlockSlicer> blockSlicer,
253 DatasetFillerProvider dfp,
254 MPI_Comm comm)
255 : communicator{comm}
256 , totalExtent{std::move(tExtent)}
257 , m_blockSlicer{std::move(blockSlicer)}
258 , m_dfp{dfp}
259 , m_basePath{std::move(basePath)}
260{
261 if (m_blockSlicer == nullptr)
262 throw std::runtime_error("Argument blockSlicer cannot be a nullptr!");
263}
264
265template <typename DatasetFillerProvider>
266std::pair<Offset, Extent> MPIBenchmark<DatasetFillerProvider>::slice(int size)
267{
268 int actualSize;
269 MPI_Comm_size(this->communicator, &actualSize);
270 int rank;
271 MPI_Comm_rank(this->communicator, &rank);
272 size = std::min(size, actualSize);
273 return m_blockSlicer->sliceBlock(totalExtent, size, rank);
274}
275
276template <typename DatasetFillerProvider>
278 std::string jsonConfig,
279 std::string backend,
280 Datatype dt,
281 Series::IterationIndex_t iterations,
282 int threadSize)
283{
284 this->m_configurations.emplace_back(
285 std::move(jsonConfig), backend, threadSize, dt, iterations);
286}
287
288template <typename DatasetFillerProvider>
290 std::string jsonConfig,
291 std::string backend,
292 Datatype dt,
293 Series::IterationIndex_t iterations)
294{
295 int size;
296 MPI_Comm_size(communicator, &size);
297 addConfiguration(std::move(jsonConfig), backend, dt, iterations, size);
298}
299
300template <typename DatasetFillerProvider>
301void MPIBenchmark<DatasetFillerProvider>::resetConfigurations()
302{
303 this->m_configurations.clear();
304}
305
306template <typename DatasetFillerProvider>
307template <typename Clock>
308template <typename T>
309typename Clock::duration
310MPIBenchmark<DatasetFillerProvider>::BenchmarkExecution<Clock>::writeBenchmark(
311 std::string const &jsonConfig,
312 Offset &offset,
313 Extent &extent,
314 std::string const &extension,
315 std::shared_ptr<DatasetFiller<T>> datasetFiller,
316 Series::IterationIndex_t iterations)
317{
318 MPI_Barrier(m_benchmark->communicator);
319 auto start = Clock::now();
320
321 // open file for writing
322 Series series = Series(
323 m_benchmark->m_basePath + "." + extension,
324 Access::CREATE,
325 m_benchmark->communicator,
326 jsonConfig);
327
328 typename Clock::duration ignore{};
329
330 for (Series::IterationIndex_t i = 0; i < iterations; i++)
331 {
332 auto ignore_start = Clock::now();
333 auto writeData = datasetFiller->produceData();
334 auto ignore_end = Clock::now();
335 ignore += ignore_end - ignore_start;
336
337 MeshRecordComponent id = series.writeIterations()[i]
338 .meshes["id"][MeshRecordComponent::SCALAR];
339
340 Datatype datatype = determineDatatype(writeData);
341 Dataset dataset = Dataset(datatype, m_benchmark->totalExtent);
342
343 id.resetDataset(dataset);
344
345 series.flush();
346
347 id.storeChunk<T>(writeData, offset, extent);
348 series.flush();
349 }
350 series.close();
351 MPI_Barrier(m_benchmark->communicator);
352 auto end = Clock::now();
353
354 return (end - start) - ignore;
355}
356
357template <typename DatasetFillerProvider>
358template <typename Clock>
359template <typename T>
360typename Clock::duration
361MPIBenchmark<DatasetFillerProvider>::BenchmarkExecution<Clock>::readBenchmark(
362 Offset &offset,
363 Extent &extent,
364 std::string extension,
365 Series::IterationIndex_t iterations)
366{
367 MPI_Barrier(m_benchmark->communicator);
368 // let every thread measure time
369 auto start = Clock::now();
370
371 Series series = Series(
372 m_benchmark->m_basePath + "." + extension,
373 Access::READ_ONLY,
374 m_benchmark->communicator);
375
376 for (Series::IterationIndex_t i = 0; i < iterations; i++)
377 {
379 series.snapshots()[i].meshes["id"][MeshRecordComponent::SCALAR];
380
381 auto chunk_data = id.loadChunk<T>(offset, extent);
382 series.flush();
383 }
384
385 MPI_Barrier(m_benchmark->communicator);
386 auto end = Clock::now();
387 return end - start;
388}
389
390template <typename DatasetFillerProvider>
391template <typename Clock>
392template <typename T>
393void MPIBenchmark<DatasetFillerProvider>::BenchmarkExecution<Clock>::call(
394 BenchmarkExecution<Clock> &exec,
396 int rootThread)
397{
398 Datatype dt = determineDatatype<T>();
399 auto dsf = std::dynamic_pointer_cast<DatasetFiller<T>>(
400 exec.m_benchmark->m_dfp.template operator()<T>());
401 for (auto const &config : exec.m_benchmark->m_configurations)
402 {
403 std::string jsonConfig;
404 std::string backend;
405 int size;
406 Datatype dt2;
407 Series::IterationIndex_t iterations;
408 std::tie(jsonConfig, backend, size, dt2, iterations) = config;
409
410 if (dt != dt2)
411 {
412 continue;
413 }
414
415 auto localCuboid = exec.m_benchmark->slice(size);
416
417 extentT blockSize = 1;
418 for (auto ext : localCuboid.second)
419 {
420 blockSize *= ext;
421 }
422 dsf->setNumberOfItems(blockSize);
423
424 auto writeTime = exec.writeBenchmark<T>(
425 jsonConfig,
426 localCuboid.first,
427 localCuboid.second,
428 backend,
429 dsf,
430 iterations);
431 auto readTime = exec.readBenchmark<T>(
432 localCuboid.first, localCuboid.second, backend, iterations);
433 report.addReport(
434 rootThread,
435 jsonConfig,
436 backend,
437 size,
438 dt2,
439 iterations,
440 std::make_pair(writeTime, readTime));
441 }
442}
443} // namespace openPMD
444
445#endif
Definition Dataset.hpp:38
Class representing a benchmark.
Definition MPIBenchmark.hpp:58
void addConfiguration(std::string jsonConfig, std::string backend, Datatype dt, Series::IterationIndex_t iterations, int threadSize)
Definition MPIBenchmark.hpp:277
Extent totalExtent
Total extent of the hypercuboid used in the benchmark.
Definition MPIBenchmark.hpp:67
MPIBenchmark(std::string basePath, Extent tExtent, std::shared_ptr< BlockSlicer > blockSlicer, DatasetFillerProvider dfp, MPI_Comm comm=MPI_COMM_WORLD)
Construct an MPI benchmark manually.
Definition MPIBenchmark.hpp:249
MPIBenchmarkReport< typename Clock::duration > runBenchmark(int rootThread=0)
Main function for running a benchmark.
Definition MPIBenchmark.hpp:230
Definition MeshRecordComponent.hpp:36
std::shared_ptr< T > loadChunk(Offset={0u}, Extent={-1u})
Load and allocate a chunk of data.
Definition RecordComponent.cpp:754
Implementation for the root level of the openPMD hierarchy.
Definition Series.hpp:288
Iteration::IterationIndex_t IterationIndex_t
An unsigned integer type, used to identify Iterations in a Series.
Definition Series.hpp:383
Public definitions of openPMD-api.
Definition Date.cpp:29
@ T
time
Definition UnitDimension.hpp:41
Datatype
Concrete datatype of an object available at runtime.
Definition Datatype.hpp:51
constexpr auto switchDatasetType(Datatype dt, Args &&...args) -> decltype(Action::template call< char >(std::forward< Args >(args)...))
Generalizes switching over an openPMD datatype.
STL namespace.
The report for a single benchmark produced by <openPMD/benchmark/mpi/MPIBenchmark>.
Definition MPIBenchmarkReport.hpp:45