A Discrete-Event Network Simulator
API
bench-scheduler.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2006 INRIA
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation;
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
18  */
19 
20 #include "ns3/core-module.h"
21 
22 #include <cmath> // sqrt
23 #include <fstream>
24 #include <iomanip>
25 #include <iostream>
26 #include <string.h>
27 #include <vector>
28 
29 using namespace ns3;
30 
32 bool g_debug = false;
33 
35 std::string g_me;
37 #define LOG(x) std::cout << x << std::endl
39 #define LOGME(x) LOG(g_me << x)
41 #define DEB(x) \
42  if (g_debug) \
43  { \
44  LOGME(x); \
45  }
46 
48 int g_fwidth = 6;
49 
58 class Bench
59 {
60  public:
66  Bench(const uint64_t population, const uint64_t total)
67  : m_population(population),
68  m_total(total),
69  m_count(0)
70  {
71  }
72 
80  {
81  m_rand = stream;
82  }
83 
89  void SetPopulation(const uint64_t population)
90  {
91  m_population = population;
92  }
93 
98  void SetTotal(const uint64_t total)
99  {
100  m_total = total;
101  }
102 
104  struct Result
105  {
106  double init;
107  double simu;
108  uint64_t pop;
109  uint64_t events;
110  };
111 
117  Result Run();
118 
119  private:
124  void Cb();
125 
127  uint64_t m_population;
128  uint64_t m_total;
129  uint64_t m_count;
131 }; // class Bench
132 
135 {
136  SystemWallClockMs timer;
137  double init;
138  double simu;
139 
140  DEB("initializing");
141  m_count = 0;
142 
143  timer.Start();
144  for (uint64_t i = 0; i < m_population; ++i)
145  {
146  Time at = NanoSeconds(m_rand->GetValue());
147  Simulator::Schedule(at, &Bench::Cb, this);
148  }
149  init = timer.End() / 1000.0;
150  DEB("initialization took " << init << "s");
151 
152  DEB("running");
153  timer.Start();
154  Simulator::Run();
155  simu = timer.End() / 1000.0;
156  DEB("run took " << simu << "s");
157 
159 
160  return Result{init, simu, m_population, m_count};
161 }
162 
163 void
165 {
166  if (m_count >= m_total)
167  {
168  Simulator::Stop();
169  return;
170  }
171  DEB("event at " << Simulator::Now().GetSeconds() << "s");
172 
173  Time after = NanoSeconds(m_rand->GetValue());
174  Simulator::Schedule(after, &Bench::Cb, this);
175  ++m_count;
176 }
177 
180 {
181  public:
197  BenchSuite(ObjectFactory& factory,
198  uint64_t pop,
199  uint64_t total,
200  uint64_t runs,
201  Ptr<RandomVariableStream> eventStream,
202  bool calRev);
203 
205  void Log() const;
206 
207  private:
209  void Header() const;
210 
212  struct PhaseResult
213  {
214  double time;
215  double rate;
216  double period;
217  };
218 
220  struct Result
221  {
230  static Result Bench(Bench::Result r);
231 
238  template <typename T>
239  void Log(T label) const;
240  }; // struct Result
241 
242  std::string m_scheduler;
243  std::vector<Result> m_results;
245 }; // BenchSuite
246 
247 /* static */
250 {
251  return Result{{r.init, r.pop / r.init, r.init / r.pop},
252  {r.simu, r.events / r.simu, r.simu / r.events}};
253 }
254 
255 template <typename T>
256 void
258 {
259  // Need std::left for string labels
260 
261  LOG(std::left << std::setw(g_fwidth) << label << std::setw(g_fwidth) << init.time
262  << std::setw(g_fwidth) << init.rate << std::setw(g_fwidth) << init.period
263  << std::setw(g_fwidth) << run.time << std::setw(g_fwidth) << run.rate
264  << std::setw(g_fwidth) << run.period);
265 }
266 
268  uint64_t pop,
269  uint64_t total,
270  uint64_t runs,
271  Ptr<RandomVariableStream> eventStream,
272  bool calRev)
273 {
274  Simulator::SetScheduler(factory);
275 
276  m_scheduler = factory.GetTypeId().GetName();
277  if (m_scheduler == "ns3::CalendarScheduler")
278  {
279  m_scheduler += ": insertion order: " + std::string(calRev ? "reverse" : "normal");
280  }
281  if (m_scheduler == "ns3::MapScheduler")
282  {
283  m_scheduler += " (default)";
284  }
285 
286  Bench bench(pop, total);
287  bench.SetRandomStream(eventStream);
288  bench.SetPopulation(pop);
289  bench.SetTotal(total);
290 
291  m_results.reserve(runs);
292  Header();
293 
294  // Prime
295  DEB("priming");
296  auto prime = bench.Run();
297  Result::Bench(prime).Log("prime");
298 
299  // Perform the actual runs
300  for (uint64_t i = 0; i < runs; i++)
301  {
302  auto run = bench.Run();
303  m_results.push_back(Result::Bench(run));
304  m_results.back().Log(i);
305  }
306 
308 
309 } // BenchSuite::Run
310 
311 void
313 {
314  // table header
315  LOG("");
316  LOG(m_scheduler);
317  LOG(std::left << std::setw(g_fwidth) << "Run #" << std::left << std::setw(3 * g_fwidth)
318  << "Initialization:" << std::left << "Simulation:");
319  LOG(std::left << std::setw(g_fwidth) << "" << std::left << std::setw(g_fwidth) << "Time (s)"
320  << std::left << std::setw(g_fwidth) << "Rate (ev/s)" << std::left
321  << std::setw(g_fwidth) << "Per (s/ev)" << std::left << std::setw(g_fwidth)
322  << "Time (s)" << std::left << std::setw(g_fwidth) << "Rate (ev/s)" << std::left
323  << "Per (s/ev)");
324  LOG(std::setfill('-') << std::right << std::setw(g_fwidth) << " " << std::right
325  << std::setw(g_fwidth) << " " << std::right << std::setw(g_fwidth) << " "
326  << std::right << std::setw(g_fwidth) << " " << std::right
327  << std::setw(g_fwidth) << " " << std::right << std::setw(g_fwidth) << " "
328  << std::right << std::setw(g_fwidth) << " " << std::setfill(' '));
329 }
330 
331 void
333 {
334  if (m_results.size() < 2)
335  {
336  LOG("");
337  return;
338  }
339 
340  // Average the results
341 
342  // See Welford's online algorithm for these expressions,
343  // which avoid subtracting large numbers.
344  // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
345 
346  uint64_t n{0}; // number of samples
347  Result average{m_results[0]}; // average
348  Result moment2{{0, 0, 0}, // 2nd moment, to calculate stdev
349  {0, 0, 0}};
350 
351  for (; n < m_results.size(); ++n)
352  {
353  double deltaPre;
354  double deltaPost;
355  const auto& run = m_results[n];
356  uint64_t count = n + 1;
357 
358 #define ACCUMULATE(phase, field) \
359  deltaPre = run.phase.field - average.phase.field; \
360  average.phase.field += deltaPre / count; \
361  deltaPost = run.phase.field - average.phase.field; \
362  moment2.phase.field += deltaPre * deltaPost
363 
364  ACCUMULATE(init, time);
365  ACCUMULATE(init, rate);
366  ACCUMULATE(init, period);
367  ACCUMULATE(run, time);
368  ACCUMULATE(run, rate);
369  ACCUMULATE(run, period);
370 
371 #undef ACCUMULATE
372  }
373 
374  auto stdev = Result{
375  {std::sqrt(moment2.init.time / n),
376  std::sqrt(moment2.init.rate / n),
377  std::sqrt(moment2.init.period / n)},
378  {std::sqrt(moment2.run.time / n),
379  std::sqrt(moment2.run.rate / n),
380  std::sqrt(moment2.run.period / n)},
381  };
382 
383  average.Log("average");
384  stdev.Log("stdev");
385 
386  LOG("");
387 
388 } // BenchSuite::Log()
389 
402 GetRandomStream(std::string filename)
403 {
404  Ptr<RandomVariableStream> stream = nullptr;
405 
406  if (filename.empty())
407  {
408  LOG(" Event time distribution: default exponential");
409  auto erv = CreateObject<ExponentialRandomVariable>();
410  erv->SetAttribute("Mean", DoubleValue(100));
411  stream = erv;
412  }
413  else
414  {
415  std::istream* input;
416 
417  if (filename == "-")
418  {
419  LOG(" Event time distribution: from stdin");
420  input = &std::cin;
421  }
422  else
423  {
424  LOG(" Event time distribution: from " << filename);
425  input = new std::ifstream(filename);
426  }
427 
428  double value;
429  std::vector<double> nsValues;
430 
431  while (!input->eof())
432  {
433  if (*input >> value)
434  {
435  auto ns = (uint64_t)(value * 1000000000);
436  nsValues.push_back(ns);
437  }
438  else
439  {
440  input->clear();
441  std::string line;
442  *input >> line;
443  }
444  }
445  LOG(" Found " << nsValues.size() << " entries");
446  auto drv = CreateObject<DeterministicRandomVariable>();
447  drv->SetValueArray(&nsValues[0], nsValues.size());
448  stream = drv;
449  }
450 
451  return stream;
452 }
453 
454 int
455 main(int argc, char* argv[])
456 {
457  bool allSched = false;
458  bool schedCal = false;
459  bool schedHeap = false;
460  bool schedList = false;
461  bool schedMap = false; // default scheduler
462  bool schedPQ = false;
463 
464  uint64_t pop = 100000;
465  uint64_t total = 1000000;
466  uint64_t runs = 1;
467  std::string filename = "";
468  bool calRev = false;
469 
470  CommandLine cmd(__FILE__);
471  cmd.Usage("Benchmark the simulator scheduler.\n"
472  "\n"
473  "Event intervals are taken from one of:\n"
474  " an exponential distribution, with mean 100 ns,\n"
475  " an ascii file, given by the --file=\"<filename>\" argument,\n"
476  " or standard input, by the argument --file=\"-\"\n"
477  "In the case of either --file form, the input is expected\n"
478  "to be ascii, giving the relative event times in ns.\n"
479  "\n"
480  "If no scheduler is specified the MapScheduler will be run.");
481  cmd.AddValue("all", "use all schedulers", allSched);
482  cmd.AddValue("cal", "use CalendarScheduler", schedCal);
483  cmd.AddValue("calrev", "reverse ordering in the CalendarScheduler", calRev);
484  cmd.AddValue("heap", "use HeapScheduler", schedHeap);
485  cmd.AddValue("list", "use ListScheduler", schedList);
486  cmd.AddValue("map", "use MapScheduler (default)", schedMap);
487  cmd.AddValue("pri", "use PriorityQueue", schedPQ);
488  cmd.AddValue("debug", "enable debugging output", g_debug);
489  cmd.AddValue("pop", "event population size", pop);
490  cmd.AddValue("total", "total number of events to run", total);
491  cmd.AddValue("runs", "number of runs", runs);
492  cmd.AddValue("file", "file of relative event times", filename);
493  cmd.AddValue("prec", "printed output precision", g_fwidth);
494  cmd.Parse(argc, argv);
495 
496  g_me = cmd.GetName() + ": ";
497  g_fwidth += 6; // 5 extra chars in '2.000002e+07 ': . e+0 _
498 
499  LOG(std::setprecision(g_fwidth - 6)); // prints blank line
500  LOGME(" Benchmark the simulator scheduler");
501  LOG(" Event population size: " << pop);
502  LOG(" Total events per run: " << total);
503  LOG(" Number of runs per scheduler: " << runs);
504  DEB("debugging is ON");
505 
506  if (allSched)
507  {
508  schedCal = schedHeap = schedList = schedMap = schedPQ = true;
509  }
510  // Set the default case if nothing else is set
511  if (!(schedCal || schedHeap || schedList || schedMap || schedPQ))
512  {
513  schedMap = true;
514  }
515 
516  auto eventStream = GetRandomStream(filename);
517 
518  ObjectFactory factory("ns3::MapScheduler");
519  if (schedCal)
520  {
521  factory.SetTypeId("ns3::CalendarScheduler");
522  factory.Set("Reverse", BooleanValue(calRev));
523  BenchSuite(factory, pop, total, runs, eventStream, calRev).Log();
524  if (allSched)
525  {
526  factory.Set("Reverse", BooleanValue(!calRev));
527  BenchSuite(factory, pop, total, runs, eventStream, !calRev).Log();
528  }
529  }
530  if (schedHeap)
531  {
532  factory.SetTypeId("ns3::HeapScheduler");
533  BenchSuite(factory, pop, total, runs, eventStream, calRev).Log();
534  }
535  if (schedList)
536  {
537  factory.SetTypeId("ns3::ListScheduler");
538  auto listTotal = total;
539  if (allSched)
540  {
541  LOG("Running List scheduler with 1/10 total events");
542  listTotal /= 10;
543  }
544  BenchSuite(factory, pop, listTotal, runs, eventStream, calRev).Log();
545  }
546  if (schedMap)
547  {
548  factory.SetTypeId("ns3::MapScheduler");
549  BenchSuite(factory, pop, total, runs, eventStream, calRev).Log();
550  }
551  if (schedPQ)
552  {
553  factory.SetTypeId("ns3::PriorityQueueScheduler");
554  BenchSuite(factory, pop, total, runs, eventStream, calRev).Log();
555  }
556 
557  return 0;
558 }
Ptr< RandomVariableStream > GetRandomStream(std::string filename)
Create a RandomVariableStream to generate next event delays.
bool g_debug
Flag to write debugging output.
#define LOGME(x)
Log with program name prefix.
int g_fwidth
Output field width for numeric data.
std::string g_me
Name of this program.
#define ACCUMULATE(phase, field)
#define LOG(x)
Log to std::cout.
#define DEB(x)
Log debugging output.
Benchmark instance which can do a single run.
void SetRandomStream(Ptr< RandomVariableStream > stream)
Set the event delay interval random stream.
uint64_t m_population
Event population size.
void SetTotal(const uint64_t total)
Set the total number of events to execute.
Result Run()
Run the benchmark as configured.
Ptr< RandomVariableStream > m_rand
Stream for event delays.
uint64_t m_count
Count of events executed so far.
Bench(const uint64_t population, const uint64_t total)
Constructor.
void SetPopulation(const uint64_t population)
Set the number of events to populate the scheduler with.
uint64_t m_total
Total number of events to execute.
void Cb()
Event function.
Benchmark which performs an ensemble of runs.
void Log() const
Write the results to LOG()
BenchSuite(ObjectFactory &factory, uint64_t pop, uint64_t total, uint64_t runs, Ptr< RandomVariableStream > eventStream, bool calRev)
Perform the runs for a single scheduler type.
void Header() const
Print the table header.
std::vector< Result > m_results
Store for the run results.
std::string m_scheduler
Descriptive string for the scheduler.
Parse command-line arguments.
Definition: command-line.h:232
This class can be used to hold variables of floating point type such as 'double' or 'float'.
Definition: double.h:42
Protocol header serialization and deserialization.
Definition: header.h:44
Instantiate subclasses of ns3::Object.
TypeId GetTypeId() const
Get the TypeId which will be created by this ObjectFactory.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition: simulator.h:571
static void Destroy()
Execute the events scheduled with ScheduleDestroy().
Definition: simulator.cc:142
static Time Now()
Return the current simulation virtual time.
Definition: simulator.cc:208
static void Run()
Run the simulation.
Definition: simulator.cc:178
static void SetScheduler(ObjectFactory schedulerFactory)
Set the scheduler type with an ObjectFactory.
Definition: simulator.cc:164
static void Stop()
Tell the Simulator the calling event should be the last one executed.
Definition: simulator.cc:186
Measure elapsed wall clock time in milliseconds.
int64_t End()
Stop measuring the time since Start() was called.
void Start()
Start a measure.
Simulation virtual time values and global simulation resolution.
Definition: nstime.h:105
std::string GetName() const
Get the name.
Definition: type-id.cc:991
Time NanoSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1362
Every class exported by the ns3 library is enclosed in the ns3 namespace.
SpectrumValue Log(const SpectrumValue &arg)
cmd
Definition: second.py:40
ns3::StringValue attribute value declarations.
uint64_t events
Number of events executed.
uint64_t pop
Event population.
double init
Time (s) for initialization.
double simu
Time (s) for simulation.
Statistics from a single phase, init or run.
double rate
Phase event rate (events/s).
double period
Phase period (s/event).
double time
Phase run time time (s).
Results from initialization and execution of a single run.
PhaseResult init
Initialization phase results.
static Result Bench(Bench::Result r)
Construct from the individual run result.
void Log(T label) const
Log this result.
PhaseResult run
Run (simulation) phase results.