A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
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
29using namespace ns3;
30
31/** Flag to write debugging output. */
32bool g_debug = false;
33
34/** Name of this program. */
35std::string g_me;
36/** Log to std::cout */
37#define LOG(x) std::cout << x << std::endl
38/** Log with program name prefix. */
39#define LOGME(x) LOG(g_me << x)
40/** Log debugging output. */
41#define DEB(x) \
42 if (g_debug) \
43 { \
44 LOGME(x); \
45 }
46
47/** Output field width for numeric data. */
48int g_fwidth = 6;
49
50/**
51 * Benchmark instance which can do a single run.
52 *
53 * The run is controlled by the event population size and
54 * total number of events, which are set at construction.
55 *
56 * The event distribution in time is set by SetRandomStream()
57 */
58class Bench
59{
60 public:
61 /**
62 * Constructor
63 * \param [in] population The number of events to keep in the scheduler.
64 * \param [in] total The total number of events to execute.
65 */
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
73 /**
74 * Set the event delay interval random stream.
75 *
76 * \param [in] stream The random variable stream to be used to generate
77 * delays for future events.
78 */
80 {
81 m_rand = stream;
82 }
83
84 /**
85 * Set the number of events to populate the scheduler with.
86 * Each event executed schedules a new event, maintaining the population.
87 * \param [in] population The number of events to keep in the scheduler.
88 */
89 void SetPopulation(const uint64_t population)
90 {
91 m_population = population;
92 }
93
94 /**
95 * Set the total number of events to execute.
96 * \param [in] total The total number of events to execute.
97 */
98 void SetTotal(const uint64_t total)
99 {
100 m_total = total;
101 }
102
103 /** The output. */
104 struct Result
105 {
106 double init; /**< Time (s) for initialization. */
107 double simu; /**< Time (s) for simulation. */
108 uint64_t pop; /**< Event population. */
109 uint64_t events; /**< Number of events executed. */
110 };
111
112 /**
113 * Run the benchmark as configured.
114 *
115 * \returns The Result.
116 */
117 Result Run();
118
119 private:
120 /**
121 * Event function. This checks for completion (total number of events
122 * executed) and schedules a new event if not complete.
123 */
124 void Cb();
125
126 Ptr<RandomVariableStream> m_rand; /**< Stream for event delays. */
127 uint64_t m_population; /**< Event population size. */
128 uint64_t m_total; /**< Total number of events to execute. */
129 uint64_t m_count; /**< Count of events executed so far. */
130
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 {
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();
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
163void
165{
166 if (m_count >= m_total)
167 {
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
178/** Benchmark which performs an ensemble of runs. */
180{
181 public:
182 /**
183 * Perform the runs for a single scheduler type.
184 *
185 * This will create and set the scheduler, then execute a priming run
186 * followed by the number of data runs requested.
187 *
188 * Output will be in the form of a table showing performance for each run.
189 *
190 * \param [in] factory Factory pre-configured to create the desired Scheduler.
191 * \param [in] pop The event population size.
192 * \param [in] total The total number of events to execute.
193 * \param [in] runs The number of replications.
194 * \param [in] eventStream The random stream of event delays.
195 * \param [in] calRev For the CalendarScheduler, whether the Reverse attribute was set.
196 */
197 BenchSuite(ObjectFactory& factory,
198 uint64_t pop,
199 uint64_t total,
200 uint64_t runs,
201 Ptr<RandomVariableStream> eventStream,
202 bool calRev);
203
204 /** Write the results to \c LOG() */
205 void Log() const;
206
207 private:
208 /** Print the table header. */
209 void Header() const;
210
211 /** Statistics from a single phase, init or run. */
213 {
214 double time; /**< Phase run time time (s). */
215 double rate; /**< Phase event rate (events/s). */
216 double period; /**< Phase period (s/event). */
217 };
218
219 /** Results from initialization and execution of a single run. */
220 struct Result
221 {
222 PhaseResult init; /**< Initialization phase results. */
223 PhaseResult run; /**< Run (simulation) phase results. */
224 /**
225 * Construct from the individual run result.
226 *
227 * \param [in] r The result from a single run.
228 * \returns The run result.
229 */
230 static Result Bench(Bench::Result r);
231
232 /**
233 * Log this result.
234 *
235 * \tparam T The type of the label.
236 * \param label The label for the line.
237 */
238 template <typename T>
239 void Log(T label) const;
240 }; // struct Result
241
242 std::string m_scheduler; /**< Descriptive string for the scheduler. */
243 std::vector<Result> m_results; /**< Store for the run results. */
244
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
255template <typename T>
256void
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{
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
311void
313{
314 // table header
315 LOG("");
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
331void
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
390/**
391 * Create a RandomVariableStream to generate next event delays.
392 *
393 * If the \p filename parameter is empty a default exponential time
394 * distribution will be used, with mean delay of 100 ns.
395 *
396 * If the \p filename is `-` standard input will be used.
397 *
398 * \param [in] filename The delay interval source file name.
399 * \returns The RandomVariableStream.
400 */
402GetRandomStream(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
454int
455main(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.
AttributeValue implementation for Boolean.
Definition: boolean.h:37
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
Instantiate subclasses of ns3::Object.
TypeId GetTypeId() const
Get the TypeId which will be created by this ObjectFactory.
Smart pointer class similar to boost::intrusive_ptr.
Definition: ptr.h:77
virtual double GetValue()=0
Get the next random value drawn from the distribution.
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:992
Time NanoSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1355
Every class exported by the ns3 library is enclosed in the ns3 namespace.
ns 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.