/* -*- Mode:C++; c-basic-offset:8; tab-width:8; -*- */
/*
* Copyright (c) 1997 David Wetherall
* Copyright (c) 2005 David Wei
* Copyright (c) 2009 INRIA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "ns2-calendar-scheduler.h"
#include "event-impl.h"
#include "ns3/assert.h"
#include "ns3/log.h"
#include <cassert>
#include <math.h>
#include <string.h>
namespace ns3 {
NS_LOG_COMPONENT_DEFINE ("Ns2CalendarScheduler");
NS_OBJECT_ENSURE_REGISTERED (Ns2CalendarScheduler);
#define CALENDAR_HASH(key) ((key.m_ts / width_) % nbuckets_)
TypeId
Ns2CalendarScheduler::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::Ns2CalendarScheduler")
.SetParent<Scheduler> ()
.AddConstructor<Ns2CalendarScheduler> ()
;
return tid;
}
Ns2CalendarScheduler::Ns2CalendarScheduler ()
{
NS_LOG_FUNCTION (this);
adjust_new_width_interval_ = 10;
min_bin_width_ = 1;
avg_gap_ = -2;
last_time_ = 0;
gap_num_ = 0;
head_search_ = 0;
insert_search_ = 0;
round_num_ = 0;
time_to_newwidth_ = adjust_new_width_interval_;
cal_clock_ = Scheduler::EventKey ();
reinit(4, 1, cal_clock_);
}
Ns2CalendarScheduler::~Ns2CalendarScheduler ()
{
NS_LOG_FUNCTION (this);
for (int i = 0; i < nbuckets_; i++) {
Bucket *bucket = &buckets_[i];
if (bucket->list_ == 0) {
continue;
}
if (bucket->list_->next_ == bucket->list_) {
delete bucket->list_;
continue;
}
BucketItem *next;
BucketItem *cur;
for (cur = bucket->list_;
cur->next_ != bucket->list_;
cur = next) {
next = cur->next_;
delete cur;
}
delete cur;
}
delete [] buckets_;
qsize_ = 0;
stat_qsize_ = 0;
}
void
Ns2CalendarScheduler::Insert (const Event &event)
{
NS_LOG_FUNCTION (this);
Scheduler::EventKey newtime = event.key;
int i = CALENDAR_HASH(newtime);
Bucket* current=(&buckets_[i]);
BucketItem *head = current->list_;
BucketItem *after=0;
BucketItem *e = new BucketItem ();
e->event = event;
if (!head) {
current->list_ = e;
e->next_ = e->prev_ = e;
++stat_qsize_;
++(current->count_);
} else {
insert_search_++;
if (newtime < head->event.key) {
// e-> head -> ...
e->next_ = head;
e->prev_ = head->prev_;
e->prev_->next_ = e;
head->prev_ = e;
current->list_ = e;
++stat_qsize_;
++(current->count_);
} else {
for (after = head->prev_; newtime < after->event.key; after = after->prev_)
{ insert_search_++; };
//...-> after -> e -> ...
e->next_ = after->next_;
e->prev_ = after;
e->next_->prev_ = e;
after->next_ = e;
if (after->event.key < newtime) {
//unique timing
++stat_qsize_;
++(current->count_);
}
}
}
++qsize_;
//assert(e == buckets_[i].list_ || e->prev_->time_ <= e->time_);
//assert(e == buckets_[i].list_->prev_ || e->next_->time_ >= e->time_);
if (stat_qsize_ > top_threshold_) {
resize(nbuckets_ << 1, cal_clock_);
return;
}
}
bool
Ns2CalendarScheduler::IsEmpty (void) const
{
return qsize_ == 0;
}
Scheduler::Event
Ns2CalendarScheduler::PeekNext (void) const
{
NS_LOG_FUNCTION (this);
NS_ASSERT (!IsEmpty ());
BucketItem *e = const_cast<Ns2CalendarScheduler *> (this)->head ();
NS_ASSERT (e != 0);
return e->event;
}
Scheduler::Event
Ns2CalendarScheduler::RemoveNext (void)
{
NS_LOG_FUNCTION (this);
NS_ASSERT (!IsEmpty ());
BucketItem *e = head ();
NS_ASSERT (e != 0);
if (last_time_ == 0) last_time_ = e->event.key.m_ts;
else
{
gap_num_ ++;
if (gap_num_ >= qsize_ ) {
uint64_t tt_gap_ = e->event.key.m_ts - last_time_;
avg_gap_ = tt_gap_ / gap_num_;
gap_num_ = 0;
last_time_ = e->event.key.m_ts;
round_num_ ++;
if ((round_num_ > 20) &&
(( head_search_> (insert_search_<<1))
||( insert_search_> (head_search_<<1)) ))
{
resize(nbuckets_, cal_clock_);
round_num_ = 0;
} else {
if (round_num_ > 100) {
round_num_ = 0;
head_search_ = 0;
insert_search_ = 0;
}
}
}
}
int l = lastbucket_;
// update stats and unlink
if (e->next_ == e || e->next_->event.key != e->event.key) {
--stat_qsize_;
//assert(stat_qsize_ >= 0);
--buckets_[l].count_;
//assert(buckets_[l].count_ >= 0);
}
--qsize_;
if (e->next_ == e)
buckets_[l].list_ = 0;
else {
e->next_->prev_ = e->prev_;
e->prev_->next_ = e->next_;
buckets_[l].list_ = e->next_;
}
e->next_ = e->prev_ = NULL;
//if (buckets_[l].count_ == 0)
// assert(buckets_[l].list_ == 0);
if (stat_qsize_ < bot_threshold_) {
resize(nbuckets_ >> 1, cal_clock_);
}
Scheduler::Event event = e->event;
delete e;
return event;
}
void
Ns2CalendarScheduler::Remove (const Event &ev)
{
NS_ASSERT (!IsEmpty ());
int i = CALENDAR_HASH(ev.key);
Bucket* current=(&buckets_[i]);
BucketItem *head = current->list_;
BucketItem *e=0;
if (!head) {
NS_LOG_DEBUG ("event not in scheduler");
return;
}
for (e = head->prev_; ev.key != e->event.key; e = e->prev_) {}
--stat_qsize_;
--buckets_[i].count_;
if (e->next_ == e) {
assert(buckets_[i].list_ == e);
buckets_[i].list_ = 0;
} else {
e->next_->prev_ = e->prev_;
e->prev_->next_ = e->next_;
if (buckets_[i].list_ == e)
buckets_[i].list_ = e->next_;
}
if (buckets_[i].count_ == 0)
assert(buckets_[i].list_ == 0);
e->next_ = e->prev_ = NULL;
delete e;
--qsize_;
return;
}
Ns2CalendarScheduler::BucketItem *
Ns2CalendarScheduler::head (void)
{
NS_ASSERT (!IsEmpty ());
int l = -1, i = lastbucket_;
int lastbucket_dec = (lastbucket_) ? lastbucket_ - 1 : nbuckets_ - 1;
uint64_t diff;
BucketItem *e, *min_e = NULL;
#define CAL_DEQUEUE(x) \
do { \
head_search_++; \
if ((e = buckets_[i].list_) != NULL) { \
diff = e->event.key.m_ts - cal_clock_.m_ts; \
if (diff < diff##x##_) { \
l = i; \
goto found_l; \
} \
if (min_e == NULL || min_e->event.key > e->event.key) { \
min_e = e; \
l = i; \
} \
} \
if (++i == nbuckets_) i = 0; \
} while (0)
// CAL_DEQUEUE applied successively will find the event to
// dequeue (within one year) and keep track of the
// minimum-priority event seen so far; the argument controls
// the criteria used to decide whether the event should be
// considered `within one year'. Importantly, the number of
// buckets should not be less than 4.
CAL_DEQUEUE(0);
CAL_DEQUEUE(1);
for (; i != lastbucket_dec; ) {
CAL_DEQUEUE(2);
}
// one last bucket is left unchecked - take the minimum
// [could have done CAL_DEQUEUE(3) with diff3_ = bwidth*(nbuck*3/2-1)]
e = buckets_[i].list_;
if (min_e != NULL &&
(e == NULL || min_e->event.key < e->event.key))
e = min_e;
else {
//assert(e);
l = i;
}
found_l:
//assert(buckets_[l].count_ >= 0);
//assert(buckets_[l].list_ == e);
/* l is the index of the bucket to dequeue, e is the event */
/*
* still want to advance lastbucket_ and cal_clock_ to save
* time when deque() follows.
*/
assert (l != -1);
lastbucket_ = l;
cal_clock_ = e->event.key;
return e;
}
void
Ns2CalendarScheduler::reinit(int nbuck, uint64_t bwidth, Scheduler::EventKey start)
{
buckets_ = new Bucket[nbuck];
memset(buckets_, 0, sizeof(Bucket)*nbuck); //faster than ctor
width_ = bwidth;
nbuckets_ = nbuck;
qsize_ = 0;
stat_qsize_ = 0;
lastbucket_ = CALENDAR_HASH(start);
diff0_ = bwidth*nbuck/2;
diff1_ = diff0_ + bwidth;
diff2_ = bwidth*nbuck;
//diff3_ = bwidth*(nbuck*3/2-1);
bot_threshold_ = (nbuck >> 1) - 2;
top_threshold_ = (nbuck << 1);
}
void
Ns2CalendarScheduler::resize(int newsize, Scheduler::EventKey start)
{
uint64_t bwidth;
if (newsize == nbuckets_) {
/* we resize for bwidth*/
if (head_search_) bwidth = head_search_; else bwidth = 1;
if (insert_search_) bwidth = bwidth / insert_search_;
bwidth = static_cast<uint64_t> (sqrt (bwidth) * width_);
if (bwidth < min_bin_width_) {
if (time_to_newwidth_>0) {
time_to_newwidth_ --;
head_search_ = 0;
insert_search_ = 0;
round_num_ = 0;
return; //failed to adjust bwidth
} else {
// We have many (adjust_new_width_interval_) times failure in adjusting bwidth.
// should do a reshuffle with newwidth
bwidth = newwidth(newsize);
}
}
//snoopy queue calculation
} else {
/* we resize for size */
bwidth = newwidth(newsize);
if (newsize < 4)
newsize = 4;
}
Bucket *oldb = buckets_;
int oldn = nbuckets_;
reinit(newsize, bwidth, start);
// copy events to new buckets
int i;
for (i = 0; i < oldn; ++i) {
// we can do inserts faster, if we use insert2, but to
// preserve FIFO, we have to start from the end of
// each bucket and use insert2
if (oldb[i].list_) {
BucketItem *tail = oldb[i].list_->prev_;
BucketItem *e = tail;
do {
BucketItem* ep = e->prev_;
e->next_ = e->prev_ = 0;
insert2(e);
e = ep;
} while (e != tail);
}
}
head_search_ = 0;
insert_search_ = 0;
round_num_ = 0;
delete [] oldb;
}
void
Ns2CalendarScheduler::insert2(Ns2CalendarScheduler::BucketItem* e)
{
// Same as insert, but for inserts e *before* any same-time-events, if
// there should be any. Since it is used only by CalendarScheduler::newwidth(),
// some important checks present in insert() need not be performed.
int i = CALENDAR_HASH(e->event.key);
BucketItem *head = buckets_[i].list_;
BucketItem *before=0;
if (!head) {
buckets_[i].list_ = e;
e->next_ = e->prev_ = e;
++stat_qsize_;
++buckets_[i].count_;
} else {
bool newhead;
if (e->event.key > head->prev_->event.key) { //strict LIFO, so > and not >=
// insert at the tail
before = head;
newhead = false;
} else {
// insert event in time sorted order, LIFO for sim-time events
for (before = head; e->event.key > before->event.key; before = before->next_)
;
newhead = (before == head);
}
e->next_ = before;
e->prev_ = before->prev_;
before->prev_ = e;
e->prev_->next_ = e;
if (newhead) {
buckets_[i].list_ = e;
//assert(e->time_ <= e->next_->time_);
}
if (e != e->next_ && e->next_->event.key != e->event.key) {
// unique timing
++stat_qsize_;
++buckets_[i].count_;
}
}
//assert(e == buckets_[i].list_ || e->prev_->time_ <= e->time_);
//assert(e == buckets_[i].list_->prev_ || e->next_->time_ >= e->time_);
++qsize_;
// no need to check resizing
}
uint64_t
Ns2CalendarScheduler::newwidth(int newsize)
{
if (adjust_new_width_interval_) {
time_to_newwidth_ = adjust_new_width_interval_;
if (avg_gap_ > 0) return avg_gap_*4;
}
int i;
int max_bucket = 0; // index of the fullest bucket
for (i = 1; i < nbuckets_; ++i) {
if (buckets_[i].count_ > buckets_[max_bucket].count_)
max_bucket = i;
}
int nsamples = buckets_[max_bucket].count_;
if (nsamples <= 4) return width_;
uint64_t nw = (buckets_[max_bucket].list_->prev_->event.key.m_ts
- buckets_[max_bucket].list_->event.key.m_ts) * 4;
nw /= ((newsize < nsamples) ? newsize : nsamples); // min (newsize, nsamples)
nw = std::max (nw, min_bin_width_);
return nw;
}
} // namespace ns3