Lumiera  0.pre.03
»edit your freedom«
random-concurrent-test.cpp
Go to the documentation of this file.
1 /*
2  RandomConcurrent(Test) - investigate concurrent random number generation
3 
4  Copyright (C)
5  2024, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
20 #include "lib/test/run.hpp"
21 #include "lib/sync-barrier.hpp"
22 #include "lib/random.hpp"
23 #include "lib/thread.hpp"
24 #include "lib/sync.hpp"
25 #include "lib/util.hpp"
28 #include "lib/format-string.hpp"
29 #include "lib/format-cout.hpp"
31 
32 #include <deque>
33 #include <tuple>
34 using std::tuple;
35 using std::deque;
36 using util::_Fmt;
37 
38 namespace lib {
39 namespace test {
40 
41  namespace {
42  const uint NUM_THREADS = 8;
43  const uint NUM_SAMPLES = 80;
44  const uint NUM_INVOKES = 1'000'000;
45  }
46 
47 
48  /******************************************************************/
53  class RandomConcurrent_test : public Test
54  {
55 
56  virtual void
57  run (Arg arg)
58  {
59  seedRand();
61  if ("quick" != firstTok (arg))
63  }
64 
65 
72  void
74  {
75  auto do_nothing = []{ /* take it easy */ };
76  auto mersenne64 = []{ return rani(); };
77  auto legacy_gen = []{ return rand(); };
78  std::random_device entropySource{"/dev/urandom"};
79  auto rly_random = [&]{ return entropySource(); };
80 
81  _Fmt resultDisplay{"µ-bench(%s)%|45T.| %5.3f µs"};
82 
83  double d1 = microBenchmark (do_nothing, NUM_INVOKES).first;
84  cout << resultDisplay % "(empty call)" % d1 <<endl;
85 
86  double d2 = microBenchmark (mersenne64, NUM_INVOKES).first;
87  cout << resultDisplay % "Mersenne-64" % d2 <<endl;
88 
89  double d3 = microBenchmark (legacy_gen, NUM_INVOKES).first;
90  cout << resultDisplay % "std::rand()" % d3 <<endl;
91 
92  double d4 = microBenchmark (rly_random, NUM_INVOKES).first;
93  cout << resultDisplay % "/dev/urandom" % d4 <<endl;
94 
95  CHECK (d3 < d2 and d2 < d4);
96  }
97 
98 
113  template<typename GEN, uint threads>
114  struct Experiment
115  : Sync<>
116  {
117  deque<tuple<double,uint>> results;
118 
119  void
120  recordRun (double err, uint fails)
121  {
122  Lock sync(this);
123  results.emplace_back (err, fails);
124  }
125 
126 
127  GEN generator;
128 
129  Experiment(GEN&& fun)
130  : generator{move (fun)}
131  { }
132 
133  const uint N = NUM_INVOKES;
134  const uint REPEATS = NUM_SAMPLES / threads;
135  using ResVal = typename GEN::result_type;
136  ResVal expect = (GEN::max() - GEN::min()) / 2;
137 
138  /* === Measurement Results === */
139  double percentGlitches{0.0};
140  double percentTilted {0.0};
141  bool isFailure {false};
142 
144  void
146  {
147  auto drawRandom = [&]()
148  {
149  uint fail{0};
150  double avg{0.0};
151  for (uint i=0; i<N; ++i)
152  {
153  auto r = generator();
154  if (r < GEN::min() or r > GEN::max())
155  ++fail;
156  avg += 1.0/N * r;
157  }
158  auto error = avg/expect - 1;
159  recordRun (error, fail);
160  };
161 
162  threadBenchmark<threads> (drawRandom, REPEATS);
163 
164  uint cases{0}, lows{0}, glitches{0};
165  _Fmt resultLine{"%6.3f ‰ : %d %s"};
166  for (auto [err,fails] : results)
167  {
168  bool isGlitch = fails or fabs(err) > 3 * 1/sqrt(N); // mean of a sound distribution will remain within bounds
169  cout << resultLine % (err*1000)
170  % fails
171  % (fails? "FAIL": isGlitch? " !! ":"") << endl;
172  ++cases;
173  if (err < 0) ++lows;
174  if (isGlitch) ++glitches;
175  }
176  // assess overall results......
177  percentGlitches = 100.0 * glitches/cases;
178  percentTilted = 100.0 * fabs(double(lows)/cases - 0.5)*2; // degree to which mean is biased for one side
179  isFailure = glitches or percentTilted > 30; // (empirical trigger criterion)
180  cout << _Fmt{"++-------------++ %s\n"
181  " Glitches: %5.1f %%\n"
182  " Tilted: %5.1f %%\n"
183  "++-------------++\n"}
184  % (isFailure? "FAIL": "(ok)")
185  % percentGlitches
186  % percentTilted
187  << endl;
188  }
189  };
190 
191 
199  void
201  {
202  using Mersenne64 = std::mt19937_64;
203  using Mersenne32 = std::mt19937;
204  using CappedMs32 = CappedGen<Mersenne32>;
205 
206  Experiment<Mersenne32,1> single_mers32{Mersenne32(defaultGen.uni())};
207  Experiment<Mersenne32,NUM_THREADS> concurr_mers32{Mersenne32(defaultGen.uni())};
208  Experiment<Mersenne64,NUM_THREADS> concurr_mers64{Mersenne64(defaultGen.uni())};
209  Experiment<CappedMs32,NUM_THREADS> concCap_mers32{CappedMs32(defaultGen.uni())};
210 
211  single_mers32.perform();
212  concurr_mers32.perform();
213  concurr_mers64.perform();
214  concCap_mers32.perform();
215 
216  CHECK (not single_mers32.isFailure, "ALARM : single-threaded Mersenne-Twister 32bit produces skewed distribution");
217  CHECK ( concurr_mers32.isFailure, "SURPRISE : Mersenne-Twister 32bit encountered NO glitches under concurrent pressure");
218  CHECK ( concurr_mers64.isFailure, "SURPRISE : Mersenne-Twister 64bit encountered NO glitches under concurrent pressure");
219  }
220  };
221 
222  LAUNCHER (RandomConcurrent_test, "unit common");
223 
224 
225 }} // namespace lib::test
Facility for monitor object based locking.
Definition: sync.hpp:209
Automatically use custom string conversion in C++ stream output.
Definition: run.hpp:40
Front-end for printf-style string template interpolation.
int rani(uint bound=_iBOUND())
Definition: random.hpp:135
scoped guard to control the actual locking.
Definition: sync.hpp:226
Functions to perform (multithreaded) timing measurement on a given functor.
Helpers typically used while writing tests.
A front-end for using printf-style formatting.
Implementation namespace for support and library code.
auto microBenchmark(FUN const &testSubject, const size_t repeatCnt=DEFAULT_RUNS)
perform a simple looped microbenchmark.
Managing a collection of non-copyable polymorphic objects in compact storage.
Object Monitor based synchronisation.
Simplistic test class runner.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
Research setup to investigate concurrent access to a random generator.
Convenience front-end to simplify and codify basic thread handling.
Generating (pseudo) random numbers with controlled seed.
Adapter to protect against data corruption caused by concurrent access.
Definition: random.hpp:277
double uni()
random double drawn from interval [0.0 ... 1.0[
Definition: random.hpp:232
const uint NUM_INVOKES
invocations of the target per measurment
Random defaultGen
a global default RandomSequencer for mundane purposes
Definition: random.cpp:70
A N-fold synchronisation latch using yield-wait until fulfilment.