Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
EFFICIENTPRIORITYQUEUEDATASTRUCTUREFOR
HARDWAREIMPLEMENTATION
Project Report
Submitted by
NS-AHMAD MUJTABA
NS-DANISH ASHRAF
NS-FAIZ AHMAD
DE-36-EE-SYN-A
Submitted to
MAM SIDRA
Abstract:
In computer science, a priority queue is an abstract data type which is
like a regular queue or stack data structure, but where additionally each element
has a "priority" associated with it. In a priority queue, an element with high
priority is served before an element with low priority. More precisely, Priority
queues are data structures that maintain a list of data sorted first by priority and
second by order of insertion (FIFO). These priority queues are of great practical
interest and are used in network routers, hybrid operating systems, event
simulation, embedded systems etc. Owing to their abundance use, the priority
queues are made efficient using a system of indices and removing the comparators
hence reducing size and improving performance.
Introduction:
A priority queue is a data structure that consists of multiple queues, each with a
priority. Data is inserted into the appropriate queue, based on its priority. Data is
dequeued (extracted) from the highest priority queue that is non-empty. It is a data
structure that can be used in software, for example discrete event simulation. A
discrete-event simulation (DES) models the operation of a system as a discrete
sequence of events in time. Each event occurs at a particular instant in time and
marks a change of state in the system. Likewise, priority queues also have their
application in hardware.
Network routers use priority queues implemented in hardware to sort outgoing
packets. Each packet is assigned a priority based on its quality of service (QoS)
requirement. The priority queue ensures that the packets with highest QoS are sent
first (and in FIFO order for that priority level). Embedded systems also make use
of priority queues implemented in hardware.
mQ Architecture :
The new architecture that will be followed to make the priority queues efficient is
called mQ Architecture as it implements multiple queues in a single array. An array
of n blocks is used to contain a maximum n entries. This is logically sub-divided into
multiple queues and a flexible system of indices is used to manage their locations
within the array.
Insertion
If user wants data to be added to any queue it will be inserted at the back of the
desired queue following the FIFO order. mQ architecture doesnt quite fit in this
operation, owing to the fact that each insertion in any queue is followed by shifting
of all the after elements already in the array to be shifted one step right hence
causing disturbance and increasing order of program thus causing time issues. For
example to insert into Queue 1 ,the data is inserted in element 3 of the array
(pointed to by Index 2). All array elements from position 3 to 7 shift to the right and
Indices 2to eoi increment by one.
Insertion in Queue
Deletion
Deletion of nodes from the array is also based on FIFO hence starting from front of
array with the node of queue with highest priority. To extract from a queue, values
are removed at the head, causing following array elements to shift left and following
indices to decrement by one. Again mQ architecture face time difficulties in shifting
all the elements of array by one position for each deletion.
Traversal
Traversal is executed from the index zero of the array starting from the nodes
of queue with highest priority and printing the data of each node till the end
of the array. One can directly access the elements of each queue inside the
array by using the indices stored in indices array indicating the starting and
ending index of the queue with that priority.
Modifications:
In view of the important role of priority queues in modern world technology one can
dream of a most efficient way of implementing them. Instead of using a single array
to store all the queues, every queue is given a separate circular array in the
improved version. A linker class binds all these circular arrays together. Using
composition linker class defines the number of priority queues to be implemented.
The enhanced version is free from the need of system of indices to store the
locations of priority queues as we no longer use a single array. The new proposed
system has a great advantage of having order 1 when pushing and popping
because one no longer needs to shift all the contents to left and right owing to the
use of separate and more importantly circular arrays for the priority queues. This
modified architecture is known as mcQ architecture as it implements multiple
circular queues.
Circular Arrays: The idea of a circular array is that the end of the array wraps
around to the start of the array. Mod can be used to calculate the front and back
positions in a circular array, therefore avoiding comparisons to the array size thus
reducing time. When using a fixed number of Array-Slots/Elements, it is easier to
recycle slots in a circular arrangement, because one do not need to reorder the
Elements. Whenever the first Element gets removed in an Array-Like arrangement,
one must move the remaining Elements one position to the front, so the head is not
null. But in circular Queue, one can just increase the pointer to the first Position,
7
0
Circular Array
Improved Algorithm
Insertion:
Under consideration technique uses an insertion function that takes data to
be inserted and the priority of the queue as its arguments and then adds the
given data at the end of the queue with corresponding priority. The tail of the
circular array moves one step ahead and inserts the given data at the end
following FIFO order. This whole process doesnt disturb any of the other
priority queues in the system thus improving speed.
Deletion:
Popping function included in the program starts from the head of the first
priority queue and pops the nodes one by one and moving the head pointer
one step ahead. Popping function like insertion also doesnt affect the rest of
the structure and we dont need shifting all the elements one step back as
was the case in the original architecture proposed in the research paper.
Comparison:
Computations:
Entries
Queue Data
s
mcQ
Push
Pop
mQ
Push
Pop
change
64
64
64
128
128
128
12
256
256
256
Depends on
position
Depends on
position
Depends on
position
2080
100%
8256
100%
32896
100%
Push
300
256
250
200
150
128
100
64
50
10
0
64
10
10
128
256
mQ (Depends on Position)
mcQ
Pop
35000
32896
30000
25000
20000
15000
10000
8256
5000
0 0
64
Order:
2080
64
128
128
256
256
Sr.No
1
2
Algorithm
mcQ
mQ
Push
1
Depends on position
Pop
1
n2
Memory:
Time:
Entries
Queue Data
s
4
64
mcQ
Push
Pop
568
566
128
632
648
12
256
702
689
mQ
Push
Pop
Depends on
position
Depends on
position
Depends on
position
603
615
715
Entries
mcQ
mQ
Queue Data
s
4
64
Push
Pop
Push
Pop
No change
with rise of
entries
No
change
with rise
of entries
No
change
with rise
of entries
No
change
with rise
of entries
Directly rise
with the rise
of entries
Directly rise
with the rise
of entries
Directly rise
with the rise
of entries
Directly rise
with the rise
of entries
Directly rise
with the rise
of entries
Directly rise
with the rise
of entries
128
No change
with rise of
entries
12
256
No
change with
rise of entries
Conclusion:
The mQ architecture for priority queues has been described. It is a hybrid between
an array of logically linked lists and shift register array structures. Instead of
pointers, indices are used that can increment/decrement in parallel. This avoids the
need for comparators in the array elements and the bus loading problem. Synthesis
results compare favorably with other types of data structures in terms of speed and
size.
Appendix:
mQ Architecture Code
#include<iostream>
#include<ctime>
using namespace std;
struct node {
int data; node()
{
data = 0;
}
};
class pre_que
{
public: static struct node pq[10000];//for holding data
static int arr_tail;
static int ind_tail;
static int indices[100]; //for storing adresses
//////////////////////////////////////////////
pre_que()
{
indices[ind_tail] = arr_tail;
ind_tail++;
indices[ind_tail] = arr_tail;
} ///////////////////////////////////////////////////
void push(int data,int pre)
{
for (int i = ++arr_tail; i >= indices[pre + 1]; i--)
{
pq[i].data = pq[i - 1].data;
}
pq[indices[pre + 1]].data = data;
for (int i = pre + 1; i <= ind_tail; i++)
{
indices[i]++;
}
}
int pop()
{
if (arr_tail == 0)
{
cout << "task completed" << endl;
return 0;
}
int temp = pq[0].data;
for (int i = 0; i <arr_tail; i++)
{
pq[i].data = pq[i + 1].data;
}
pq[arr_tail].data = 0;
arr_tail = arr_tail - 1;
for (int i = 0; i <= ind_tail; i++)
{
if (indices[i] <= 0);
else
{
indices[i]--;
}
}
return temp;
}
};
node pre_que::pq[10000];
int pre_que::arr_tail = 0;
int pre_que::ind_tail = 0;
int pre_que::indices[100];
void main()
{
pre_que p1;
for (int i = 0; i < 10000; i++)
{
p1.push(i, 0);
} //push and pop functions are defined in class
clock_t startTime = clock();
//anyperson want to test this program can use
for (int i = 0; i < 10000; i++)
//them to write a new main
{
//there is no limitations on using push and pop functions
cout << "poped value" << p1.pop() << endl;
}
cout << double(clock() - startTime) << " clock ticks" << endl;
}
Enhanced Code
#include<iostream>
#include<ctime>
using namespace std;
struct node
{
int data;
node()
{
data = 0;
}
};
class circular_array_queues
{
public: int size;
int ptr_pre_pop;
int ptr_pre_push;
node *c_a_q; circular_array_queues(int s = 10) //size of circular queues
{
size = s;
ptr_pre_push = 0;
ptr_pre_pop = 0;
c_a_q = new node[size];
} /////////////////////////////////////////////////
void push(int data) //this is orignal push() function
{
//for inserting data into ques and its order is 1
c_a_q[ptr_pre_push].data = data;
ptr_pre_push = (ptr_pre_push + 1) % size;
} ////////////////////////////////////////////////
int pop()
{
//this is qrignal pop() function
int temp = c_a_q[ptr_pre_pop].data;
//for inserting data into ques and its order is 1
c_a_q[ptr_pre_pop].data = 0;
ptr_pre_pop = (ptr_pre_pop + 1) % size;
return temp;
}
/////////////////////////////////////////////////
void traverse()
{
for (int i = ptr_pre_pop; i < ptr_pre_push; i++)
{
cout << "data is" << c_a_q[i].data << endl;
cout << "index is" << i << endl;
}
} ///////////////////////////////////////////////////////
~circular_array_queues()
{}
};
class linker //this class interface is only
{
//for generalized implimintation on this compiler.
public: //No linker class will be needed when we implimint this algo on
int order; //FPGA`s and other such type of hardwares
int ptr;
circular_array_queues *arr; ///////////////////////////////////////
linker(int n = 0)
{
order = n; ptr = 0;
arr = new circular_array_queues[order];
} ////////////////////////////////////////
void push(int data, int pre)
{
arr[pre].push(data);
ptr = 0;
} /////////////////////////////////////////
int pop()
{
if (ptr == order)
{
cout << "task_completed" << endl;
return 0;
}
int temp = arr[ptr].pop();
while (temp == 0)
{
arr[ptr].ptr_pre_pop--;
ptr++;
if (ptr == order)
break;
temp = arr[ptr].pop();
}
return temp;
} //////////////////////////////////////////////
void specific_que_traversal(int pre)
arr[pre].traverse();
} //////////////////////////////////////////////
void traverse_ALL_data()
{
for (int i = 0; i < order; i++)
{
arr[i].traverse();
}
} ///////////////////////////////////////////////
void delete_A_specific_que(int pre)
{
for (int i = arr[pre].ptr_pre_pop;
i < arr[pre].ptr_pre_push; i++)
{
arr[pre].pop();
}
}
};
void main()
{
int size = 10;
linker l=linker(size);
//number of ques 10
for (int i = 0; i < 10; i++)
{
l.push(i%size,i);
}
clock_t startTime = clock();
for (int i = 0; i < 10; i++)
{
cout << "poped value" << l.pop() << endl;
}
cout << double(clock() - startTime) << " clock ticks" << endl;
}