Sei sulla pagina 1di 16

NUST COLLEGE OF

ELECTRICAL MECHANICAL ENGINEERING

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.

Array holding the Data with indices Array


In Figure, the array at the top holds the data. Each index 0, ... , 3 points to the start
of a queue. Queue 0 has highest priority. Indices 0 and 1 point to the same element
(0), indicating that Queue 0 is empty. Queue 1 has three elements (0, 1, 2). Queue 2
has one element (3). Indices 3 and eoi (end of indices) point to the same element
indicating that Queue 3 is also empty. In this case the first four elements in the
array hold meaningful data and the last four are considered empty (or more
accurately, undefined). The start of each queue is determined by its index value and
the end of each queue is determined by the value of the next index.

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.

Deletion from Array

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,

hence requiring less operations on an update and gives better performance.

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:

Both codes have equal memory efficiency if we try to further improve


the memory efficiency of algorithm it will exponentially affect the order
and time efficiency of program.

Time:

On current compilers and systems we are unable to determine the


exact time efficiency of code but as it is clear from number of
computations and order, this modification takes much lesser time for
execution.

Real Time comparison table(all time values are given in ms)

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

Expected Time comparison table(all time values are given in ms)

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

Limitations & pROBLEMS:

The whole algorithm is designed to be implemented in hardware e.g


FPGAs involving digital logic so compiling it on visual studio doesnt
prove its efficiency owing to the computation time in Nano-seconds.

For some unknown reasons, in spite of significant difference in orders,


the computation time for the original and modified code is the same.
This issue needs further study.

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;
}

Potrebbero piacerti anche