Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SolenoidFieldBenchmark.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file SolenoidFieldBenchmark.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2018 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
16 
17 #include <chrono>
18 #include <fstream>
19 #include <iostream>
20 #include <random>
21 #include <string>
22 
23 using namespace Acts::UnitLiterals;
24 
25 int main(int argc, char* argv[]) {
26  size_t iters_map = 5e2;
27  size_t iters_solenoid = 3;
28  size_t runs_solenoid = 1000;
29  if (argc >= 2) {
30  iters_map = std::stoi(argv[1]);
31  }
32  if (argc >= 3) {
33  iters_solenoid = std::stoi(argv[2]);
34  }
35  if (argc >= 4) {
36  runs_solenoid = std::stoi(argv[3]);
37  }
38 
39  const double L = 5.8_m;
40  const double R = (2.56 + 2.46) * 0.5 * 0.5_m;
41  const size_t nCoils = 1154;
42  const double bMagCenter = 2_T;
43  const size_t nBinsR = 150;
44  const size_t nBinsZ = 200;
45 
46  double rMin = -0.1;
47  double rMax = R * 2.;
48  double zMin = 2 * (-L / 2.);
49  double zMax = 2 * (L / 2.);
50 
51  Acts::SolenoidBField bSolenoidField({R, L, nCoils, bMagCenter});
52  std::cout << "Building interpolated field map" << std::endl;
53  auto bFieldMap = Acts::solenoidFieldMap({rMin, rMax}, {zMin, zMax},
54  {nBinsR, nBinsZ}, bSolenoidField);
56 
57  std::minstd_rand rng;
58  std::uniform_real_distribution<> zDist(1.5 * (-L / 2.), 1.5 * L / 2.);
59  std::uniform_real_distribution<> rDist(0, R * 1.5);
60  std::uniform_real_distribution<> phiDist(-M_PI, M_PI);
61  auto genPos = [&]() -> Acts::Vector3 {
62  const double z = zDist(rng), r = rDist(rng), phi = phiDist(rng);
63  return {r * std::cos(phi), r * std::sin(phi), z};
64  };
65 
66  std::ofstream os{"bfield_bench.csv"};
67 
68  auto csv = [&](const std::string& name, auto res) {
69  os << name << "," << res.run_timings.size() << "," << res.iters_per_run
70  << "," << res.totalTime().count() << "," << res.runTimeMedian().count()
71  << "," << 1.96 * res.runTimeError().count() << ","
72  << res.iterTimeAverage().count() << ","
73  << 1.96 * res.iterTimeError().count();
74 
75  os << std::endl;
76  };
77 
78  os << "name,runs,iters,total_time,run_time_median,run_time_error,iter_"
79  "time_average,iter_time_error"
80  << std::endl;
81 
82  // SolenoidBField lookup is so slow that the cost of generating a random field
83  // lookup position is negligible in comparison...
84  std::cout << "Benchmarking random SolenoidBField lookup: " << std::flush;
85  const auto solenoid_result = Acts::Test::microBenchmark(
86  [&] { return bSolenoidField.getField(genPos()); }, iters_solenoid,
87  runs_solenoid);
88  std::cout << solenoid_result << std::endl;
89  csv("solenoid", solenoid_result);
90 
91  // ...but for interpolated B-field map, the overhead of a field lookup is
92  // comparable to that of generating a random position, so we must be more
93  // careful. Hence we do two microbenchmarks which represent a kind of
94  // lower and upper bound on field lookup performance.
95  //
96  // - The first benchmark operates at constant position, so it measures only
97  // field lookup overhead but has unrealistically good cache locality. In
98  // that sense, it provides a lower bound of field lookup performance.
99  std::cout << "Benchmarking interpolated field lookup: " << std::flush;
100  const auto fixedPos = genPos();
101  const auto map_fixed_nocache_result = Acts::Test::microBenchmark(
102  [&] { return bFieldMap.getField(fixedPos); }, iters_map);
103  std::cout << map_fixed_nocache_result << std::endl;
104  csv("interp_nocache_fixed", map_fixed_nocache_result);
105 
106  std::cout << "Benchmarking random interpolated field lookup: " << std::flush;
107 
108  // - The second benchmark generates random positions, so it is biased by the
109  // cost of random position generation and has unrealistically bad cache
110  // locality, but provides an upper bound of field lookup performance.
111  const auto map_rand_result = Acts::Test::microBenchmark(
112  [&] { return bFieldMap.getField(genPos()); }, iters_map);
113  std::cout << map_rand_result << std::endl;
114  csv("interp_nocache_random", map_rand_result);
115 
116  // - This variation of the first benchmark uses a fixed position again, but
117  // uses the cache infrastructure to evaluate how much of an impact it has on
118  // performance in this scenario. We expect this to improve performance as
119  // the cache will always be valid for the fixed point.
120  {
121  std::cout << "Benchmarking cached interpolated field lookup: "
122  << std::flush;
123  auto cache = bFieldMap.makeCache(mctx);
124  const auto map_cached_result_cache = Acts::Test::microBenchmark(
125  [&] { return bFieldMap.getField(fixedPos, cache).value(); }, iters_map);
126  std::cout << map_cached_result_cache << std::endl;
127  csv("interp_cache_fixed", map_cached_result_cache);
128  }
129 
130  // - This variation of the second benchmark again generates random positions
131  // and uses the cache infrastructure to evaluate the impact on performance.
132  // We expect this to deteriorate performance, as the cache will most likely
133  // be invalid and need to be recreated, on top of the underlying lookup.
134  {
135  std::cout << "Benchmarking cached random interpolated field lookup: "
136  << std::flush;
137  auto cache2 = bFieldMap.makeCache(mctx);
138  const auto map_rand_result_cache = Acts::Test::microBenchmark(
139  [&] { return bFieldMap.getField(genPos(), cache2).value(); },
140  iters_map);
141  std::cout << map_rand_result_cache << std::endl;
142  csv("interp_cache_random", map_rand_result_cache);
143  }
144 
145  // - The fourth benchmark tests a more 'realistic' access pattern than fixed
146  // or random positions: it advances along a straight line (which is close to
147  // a slightly curved line which happens in particle propagation). This
148  // instance does not use the cache infrastructure, so is effectively close
149  // to the random points benchmark, although positions are not really random.
150  {
151  std::cout << "Benchmarking advancing interpolated field lookup: "
152  << std::flush;
153  Acts::Vector3 pos{0, 0, 0};
154  Acts::Vector3 dir{};
155  dir.setRandom();
156  double h = 1e-3;
157  std::vector<Acts::Vector3> steps;
158  steps.reserve(iters_map);
159  for (size_t i = 0; i < iters_map; i++) {
160  pos += dir * h;
161  double z = pos[Acts::eFreePos2];
162  if (Acts::VectorHelpers::perp(pos) > rMax || z >= zMax || z < zMin) {
163  break;
164  }
165  steps.push_back(pos);
166  }
167  const auto map_adv_result = Acts::Test::microBenchmark(
168  [&](const auto& s) { return bFieldMap.getField(s); }, steps);
169  std::cout << map_adv_result << std::endl;
170  csv("interp_nocache_adv", map_adv_result);
171 
172  // - This variation of the fourth benchmark advances in a straight line, but
173  // also uses the cache infrastructure. As subsequent positions are close
174  // to one another, the cache will be valid for a certain number of points,
175  // before becoming invalid. This means we expect performance to improve
176  // over the uncached straight line advance.
177 
178  std::cout << "Benchmarking cached advancing interpolated field lookup: "
179  << std::flush;
180  auto cache = bFieldMap.makeCache(mctx);
181  const auto map_adv_result_cache = Acts::Test::microBenchmark(
182  [&](const auto& s) { return bFieldMap.getField(s, cache).value(); },
183  steps);
184  std::cout << map_adv_result_cache << std::endl;
185  csv("interp_cache_adv", map_adv_result_cache);
186  }
187 }