联系方式

  • QQ:99515681
  • 邮箱:99515681@qq.com
  • 工作时间:8:00-23:00
  • 微信:codinghelp

您当前位置:首页 >> Python编程Python编程

日期:2020-11-13 11:37

Networks & Operating Systems Essentials 2 (NOSE 2)

Assessed Exercise 2: Scheduling

The aim of this exercise is to have students use the knowledge they’ve acquired so far

in the second part of the course, in the context of implementing a set of scheduling

algorithms. You will be using and extending a simple discrete event simulator written

in Python, by creating classes that simulate the scheduling and dispatching of

processes performed by four scheduling algorithms: First-Come-First-Served (FCFS),

Shortest-Job-First (SJF), Round-Robin (RR) and Shortest-Remaining-Time-First (SRTF).

This assessed exercise will also act as a gentle introduction to such concepts as

queuing theory, discrete event simulation, and object-oriented software engineering.

Object-Oriented Software Engineering Primer

You should already be familiar with the basic concepts of object-oriented

programming in Java from the JP2 course. Specifically, you should already know:

? What a class is and what member variables and functions/methods are.

? What an object or instance of a class is.

? How to create (instantiate) an object of a class.

? How to refer to member variables/methods using dot notation (e.g.,

ClassX.func()).

? What the meaning of ‘this’ is in Java.

? What inheritance is and how you can override class methods through creating

a subclass/child class.

? What encapsulation is – the act of bundling together data/variables and

functions/methods that operate on said data/variables in a single unit: the

class.

Python also offers an object-oriented API. In Python a class is defined using the

keyword ‘class’, like so:

The equivalent of Java’s ‘this’ in Python is ‘self’. As Python is not a pure objectoriented

programming language, all class methods need to have ‘self’ as their first

argument. All variables and methods defined in this class (i.e., indented one level) are

class variables and class methods. Also, the constructor of a class does not have the

same name as the class but is rather called ‘__init__’ in Python. In Python it is also

customary to create instance variables in a class’s __init__ method.

Subclassing, or inheritance, is one of the mainstays of object-oriented programming.

Each subclass S (sometimes also referred to as a child or heir class) of a class C has

access to all members and methods of its parent class (a.k.a. ancestor class or

superclass). In Python subclassing (inheritance) is defined by including the name of

the parent class in parentheses after the name of the new child class. When a subclass

class Animal:

S has a method M with the exact same name and input parameters as those of a

superclass C, we say that S’s M overrides (in essence, provides a new default

implementation of) C’s M. In other words, if one creates an object of S and calls M on

it, it will be S’s implementation code that will be executed instead of M’s. While other

object-oriented programming languages offer mechanisms for controlling the access

to a parent class’s members and methods, Python doesn’t really have such a feature.

However, by convention subclasses shouldn’t use and/or access members/methods

whose name starts with one or more underscores.

Putting it all together, have a look at the example code below:

If you save it in a file and execute it, output should be:

class Bird:

def __init__(self, species):

self.species = species

def what_am_i(self):

print("I am a " + self.species)

def can_fly(self):

print("I can't fly")

def can_swim(self):

print("I can't swim")

class Parrot(Bird):

def __init__(self):

self.species = "Parrot"

def can_fly(self):

print("I can fly")

class Penguin(Bird):

def __init__(self):

self.species = "Penguin"

def can_swim(self):

print("I can swim")

birds = [Eagle(),Penguin()]

for bird in birds:

bird.what_am_i()

bird.can_fly()

bird.can_swim()

I am a Parrot

I can fly

I can't swim

I am a Penguin

I can't fly

I can swim

Discrete Event Simulation Primer

A discrete event simulation (DES) simulates the operation of a system via a series of

discrete events ordered in time. That is, given a set of events ordered by time of

occurrence from earliest to latest, the internal clock of the simulator does not take on

a continuum of values but rather jumps from one event to the next. The simulated

system is considered to be in a constant state in between any pair of successive events,

while every event triggers a possible change in the system’s state. The DES, in essence,

implements the following algorithm:

where service(current_event) may end up adding new events to the queue.

In the simple discrete event simulator provided for this assessed exercise, we have

three types of events:

? PROC_ ARRIVES: A new process arrives to the system.

? PROC_CPU_REQ: An existing process requests access to the CPU.

? PROC_CPU_DONE: A process has used up its service time and thus terminates.

Similarly, simulated processes can be in one of the following states:

? NEW: A process that will arrive to the system at some point in the future.

? READY: A process that is waiting for the CPU; note that this can be a new

process that just arrived, or an older process that was never scheduled or preempted.

? RUNNING: A process currently executing on the CPU.

? TERMINATED: A process that has used up its service time and is thus

terminated.

Originally the simulator creates a list of processes to be simulated. Each process has

the following attributes:

? Process ID: A number uniquely identifying the process in the system. As all

processes are added to a table (aka the process table), their ID is simply the

table index for the cell at which their info is stored, starting from 0.

? Arrival time: The time of arrival of the process.

? Service time: The total amount of CPU time required by the process.

? State: The state in which the process currently is, as discussed above.

Each process keeps track of its execution time and further offers a set of utility

functions, comprising:

? A function that returns its remaining time.

? A function that returns its departure time, if the process has terminated.

? A function that computes and returns its total waiting time.

Queue event_queue # Assume a queue of events, ordered by time

system_time = 0

while event_queue is not empty:

current_event = event_queue.remove_first_event()

if current_event.time > system_time:

system_time = current_event.time

service(current_event)

? A function that computes and returns its turnaround time.

? A function that executes the process for up to a specified amount of time.

All processes start off in the NEW state. Along the same lines, the simulator populates

the event queue with PROC_ARRIVES events for all processes to be simulated. The

simulator’s service function then looks like so (in abstract terms):

The service function makes use of two more functions, printed in italics above:

scheduler_func(event) and dispatcher_func(process). The former takes

the current event into consideration and selects the next process to be given access

to the CPU, based on the scheduling policy/algorithm. The latter takes as argument

the process selected by the scheduler and executes it on the CPU; this translates to

transitioning the process to the RUNNING state, allowing it to run for a specific amount

of time (dependent on the scheduling algorithm used), then transitioning it to either

the READY or TERMINATED state (depending on whether it used up its service time),

generating and returning an appropriate event (PROC_CPU_REQ in the former case,

PROC_CPU_DONE in the latter case). If the event is a PROC_CPU_REQ one, it is also

added to the events queue, as it will require further processing in the future. Finally,

the simulator’s internal (simulated) clock is updated to the time of the returned event.

With this in hand, you should be able to implement most simple process scheduling

algorithms, as outlined below.

The design of the simple discrete event simulator provided for this assessed exercise,

follows an object-oriented approach. The information and methods required for

events and processes are implemented in matching classes (Event, EventTypes,

Process, ProcessStates). The basic simulator logic is also implemented in a class of its

own – namely, SchedulerDES. Then, four new scheduler-specific classes are provided;

namely, FCFS, SJF, RR and SRTF, one for each of the scheduling algorithms to be

implemented. The classes are defined as subclasses of SchedulerDES.

Method overriding is used in the provided source code to allow you to implement the

various scheduling algorithms without having to touch the base discrete event

simulator implementation. You will notice that the source code of the latter includes

skeleton definitions for the scheduler and dispatcher functions discussed above, and

that these same functions are defined in the subclasses as well. You only need to edit

the latter, as it’s these implementations that will be used by the main function of the

simulator. Remember that you still have full access to all methods/members of

service(current_event):

for process in list_of_processes:

if process.arrival_time <= system_time:

process.state = READY

process_to_run = scheduler_func(current_event)

if process_to_run != currently_executing_process:

system_time += context_switch_time

currently_executing_process = process_to_run

new_event = dispatcher_func(process_to_run)

if new_event.type != PROC_CPU_DONE:

event_queue.insert(new_event)

system_time = new_event.time

SchedulerDES, but you should only really use those without one or more leading

underscores.

Scheduling Algorithms

As part of this assessed exercise, you are requested to implement the following

scheduling algorithms:

? FCFS/FIFO (non-pre-emptive): Processes should be executed in the order in

which they arrived at the system. Conceptually, when a process arrives at the

system, it is added to a queue. The scheduling algorithm will always pick the

first process in the queue and will execute it to completion. It will then proceed

with the next process in the queue, and so on.

? SJF (non-pre-emptive): Processes are prioritised based on their service time.

Conceptually, on arrival, processes are added to a priority queue, which is

sorted in ascending order of service time. The scheduling algorithm will then

always pick the first process in the queue and will execute it to completion. It

will then proceed with the next process in the queue, and so on.

? RR (pre-emptive): On arrival, processes are added to a queue. Conceptually,

the algorithm will pick the first process in the queue, execute it for a specified

amount of time (also known as a time-slice or quantum), and if the process

needs more time it will then be added to the end of the queue.

? SRTF (pre-emptive): This is a pre-emptive version of the SJF algorithm above.

Conceptually, whenever a change occurs in the system (i.e., a new process

arrives, a running process terminates, etc.), the scheduler is called to select the

process among those in the READY state with the minimum remaining

execution time. This process will then be pre-empted when a new change

occurs that results in some other process having a lower remaining execution

time than the currently executing one.

Pseudo-Random Number Generation

The main simulator code makes use of a pseudo-random number generator (PRNG)

to generate the process arrival and service times, following the M/M/1 queue

equations (as briefly discussed in last week’s tutorial). The “pseudo“ prefix in PRNG

denotes that these generators don’t really produce completely random data. Instead,

they compute a new “random” output based on a deterministic computation on state

kept internally by the PRNG implementation. Consequently, if one could control the

original internal state, they would be able to force the PRNG into producing repeatable

sequences of “random” outputs. Most implementations of PRNGs support this

functionality through seeding; in other words, they provide a method that allows the

user to define an initial seed (usually taking the form of an integer), based on which

the PRNG initialised its internal state in a deterministic manner. For example, execute

the following code in your favourite Python environment:

for repetition in range(3):

print("Repetition {}:".format(repetition))

for i in range(10):

random.random()

You should get three sequences of ten random floats, each different to the next. Then,

execute the following code:

You should now get three sequences of ten random floats, but all three sequences are

identical to each other. This is due to the fact that we have reset the seed to the same

value (1) before the generation of each of these sequences.

This is used in the provided implementation to allow for repeatable experiments. The

PRNG defaults to a random initial seed. This seed is printed to the screen when the

simulator starts. The program also features a command-line argument that allows you

to set the seed to a user-defined value, as well as arguments to enable verbose logging

of the internal state of the simulator. You can use this whenever a run of the simulator

produces counter-intuitive results, to allow you to dig into the problem and identify

the cause of the “discrepancy”. For your consideration, here is a set of “interesting”

seeds: 1797410758, 2688744162, 3399474557. These are interesting in the sense that

the respective workloads make some algorithms win over others. What you should do

in these cases, is execute the program with debug logging on, examine the sequence

of events/scheduling decisions and address the points laid out in the spec below.

Miscellanea

Start by reading and trying to understand the provided code. Please feel free to ask us

if you have any questions that cannot be answered by a simple online search. You

shouldn’t need to read up on the concepts outlined earlier (queuing theory, discrete

event simulation, object-oriented programming), but feel free to do so if you deem it

necessary to understand the provided code (or have an interest in the field).

You should only need to change the code in schedulers.py. The list of imports in said

file includes all imports that you should need for the assessed exercise. Please ask us

if you think more imports are necessary, as it might be an indication of a

misunderstanding.

Please make sure that your code is well formatted and documented, and that an

appropriate function/variable naming scheme has been used.

The main function (main.py) provides a number of command-line options that allow

you to change various parameters of the system. Executing the program with the ‘-h’

or ‘--help’ command-line arguments will print a help message explaining all

supported options. A set of sane defaults is provided in the source code, but feel free

to play around with other values. You can do so by either executing the simulator on

the command line and providing different arguments, or by changing the defaults in

the source code (main.py), or by providing your own input parameters as input to the

parser.parse_args(…) function; as an example of the latter, to execute the

program with the command-line arguments -S 851898649, do the following:

for repetition in range(3):

print("Repetition {}:".format(repetition))

random.seed(1)

for i in range(10):

random.random()

to print the help message, do the following:

to increase the verbosity level to full debug output, do the following:

and for combinations of the above, simply add more arguments to the list, like so:

Keep in mind, though, that executing your code from the command line (as in AE1) is

probably both faster and easier.

Please also note that:

? You shouldn’t need to add any new queue or other state to the simulator. All

the state required by the simulator is already there in SchedulerDES.

? If you need to walk through the list of processes, use self.processes. Given

a process ID (say, id), you can query its state via self.processes[id].X,

where X is either an attribute of the Process class or one of its methods.

? To peak ahead at the time of the next event after the current time point, use

self.next_event_time().

? If you want direct access to the events' queue (although you shouldn't need

to), use self.events_queue.

? You can also get the currently executing process (self.process_on_cpu)

and the current simulator time (self.time).

? The scheduler function is called with a single event as an argument, but it also

has full access to the process table and other internal data structures of the

simulator (e.g., events queue, etc.). In other words, the scheduler doesn't need

to decide based solely on the process that created the current event; some

scheduling algorithms do make use of this information while others completely

ignore it.

The simulator is written in such a way to facilitate abstracting out the scheduler and

dispatcher functions. As such, the code you need to write is rather minimal. As a

yardstick, the sample solution comprises less than 50 lines of code in schedulers.py

including whitespace (56 including imports) – that is, less than 7-8 new lines of code

per simulator... Your mileage may vary, of course, but these figures should give you a

rough idea of how much coding effort should go into this assessed exercise.

Sample results (random seed, timings, etc.) are provided below. In the meanwhile, if

you want to verify that your code works correctly, you should turn debug logging on

(-vv), fix the random seed to a value (e.g., -S 0) and walk your way through the trace.

If the execution trace you get by hand (i.e., process A runs first for X time units, then

process B for Y time units, etc.) does not agree with the simulator's output, then

there's something wrong; revisit your hand trace and code, rerun with the same

random seed, and see if the two agree; rinse and repeat as appropriate. Try running

the simulator with different context switch times as well, as the default value in the

simulator is set to 0.0.

args = parser.parse_args(['-S', '851898649'])

args = parser.parse_args(['-h'])

args = parser.parse_args(['-v', ‘-v’])

args = parser.parse_args(['-S', '851898649', '-v', ‘-v’])

What to submit

For this assessed exercise, you are asked to work in teams of up to two (2) students

(i.e. 2 students is the MAXIMUM – groups of more than 2 will not be considered for

marking). Submit your amended schedulers.py file, as well as a short report in pdf

format, via the course’s Moodle page (look for the “Assessed Exercise 2” assignment

link). Your report should include a heading stating your full names, matriculation

numbers and lab groups, and a discussion of your findings for the “interesting” seed

values outlined earlier (i.e., what was the relative performance of the four algorithms,

whether it deviated from your expectations, and what you believe is the cause of the

deviation based on the internal state of the simulator). Feel free to also include any

feedback you may have on the assessed exercise. Only one submission should be done

per team.

How Exercise 2 will be marked

Following timely submission on Moodle, the exercise will be given a numerical mark,

between 0 (no submission) and 100 (perfect in every way). These numerical marks will

then be converted to a band (C2 etc.)

The marking scheme is given below:

? 15 marks for each of FCFS and SJF (total: 30): 7 marks for correct selection of

the next process to execute (scheduler function), 8 marks for correct state

keeping and process execution (dispatcher function).

? 20 marks for each of RR and SRTF (total: 40): 7 marks for correct selection of

the next process to execute (scheduler function), 13 marks for correct state

keeping and process execution (dispatcher function).

? 30 marks for the report: 10 marks for the discussion of the results of each of

the “interesting” seed values.

Sample Outputs

Using seed: 1523376833

Processes to be executed:

[#0]: State: ProcessStates.NEW, Arrival: 0.8965518035211827,

Service: 0.4772854990859405, Remaining: 0.4772854990859405

[#1]: State: ProcessStates.NEW, Arrival: 1.0476559314160219,

Service: 3.177651950380589, Remaining: 3.177651950380589

[#2]: State: ProcessStates.NEW, Arrival: 1.0699502089969615,

Service: 0.6431423507594756, Remaining: 0.6431423507594756

[#3]: State: ProcessStates.NEW, Arrival: 1.133575330296856,

Service: 0.21095976155023272, Remaining: 0.21095976155023272

[#4]: State: ProcessStates.NEW, Arrival: 1.5419034712499409,

Service: 3.519113233731405, Remaining: 3.519113233731405

[#5]: State: ProcessStates.NEW, Arrival: 2.268572897370114,

Service: 6.1209365033748995, Remaining: 6.1209365033748995

[#6]: State: ProcessStates.NEW, Arrival: 2.622213160578937,

Service: 0.6057431641679517, Remaining: 0.6057431641679517

[#7]: State: ProcessStates.NEW, Arrival: 2.8181529993918173,

Service: 0.603425465615895, Remaining: 0.603425465615895

[#8]: State: ProcessStates.NEW, Arrival: 3.0484632504147853,

Service: 1.2052874280418702, Remaining: 1.2052874280418702

[#9]: State: ProcessStates.NEW, Arrival: 3.213418227642897,

Service: 2.892823843272308, Remaining: 2.892823843272308

-----

FCFS [#Processes: 10, Avg arrivals per time unit: 3.0, Avg CPU burst

time: 2, Context switch time: 0.0]:

Avg. turnaround time: 9.055465010768293

Avg. waiting time: 7.109828090770234

-----

SJF [#Processes: 10, Avg arrivals per time unit: 3.0, Avg CPU burst

time: 2, Context switch time: 0.0]:

Avg. turnaround time: 5.667330888524098

Avg. waiting time: 3.721693968526041

-----

RR [#Processes: 10, Avg arrivals per time unit: 3.0, Avg CPU burst

time: 2, Context switch time: 0.0, Quantum: 0.5]:

Avg. turnaround time: 8.6057126790477

Avg. waiting time: 6.660075759049643

-----

SRTF [#Processes: 10, Avg arrivals per time unit: 3.0, Avg CPU burst

time: 2, Context switch time: 0.0]:

Avg. turnaround time: 5.071064581670328

Avg. waiting time: 3.1254276616722705

Using seed: 3672961927

Processes to be executed:

[#0]: State: ProcessStates.NEW, Arrival: 0.9967930942538261,

Service: 1.7805632589480833, Remaining: 1.7805632589480833

[#1]: State: ProcessStates.NEW, Arrival: 1.026222231111286,

Service: 1.7022393172998072, Remaining: 1.7022393172998072

[#2]: State: ProcessStates.NEW, Arrival: 1.8916853352121672,

Service: 2.3302464408938315, Remaining: 2.3302464408938315

[#3]: State: ProcessStates.NEW, Arrival: 2.0051880828926594,

Service: 5.220529617260626, Remaining: 5.220529617260626

[#4]: State: ProcessStates.NEW, Arrival: 3.0218802368513096,

Service: 2.226205967193615, Remaining: 2.226205967193615

[#5]: State: ProcessStates.NEW, Arrival: 3.255766032372033,

Service: 4.047087113201036, Remaining: 4.047087113201036

[#6]: State: ProcessStates.NEW, Arrival: 3.3862860082130255,

Service: 3.0229686771601187, Remaining: 3.0229686771601187

[#7]: State: ProcessStates.NEW, Arrival: 3.723246678844583,

Service: 0.3318700244392102, Remaining: 0.3318700244392102

[#8]: State: ProcessStates.NEW, Arrival: 3.8174183504968853,

Service: 3.021590099896377, Remaining: 3.021590099896377

[#9]: State: ProcessStates.NEW, Arrival: 4.056820014748351,

Service: 2.448209295878759, Remaining: 2.448209295878759

-----

FCFS [#Processes: 10, Avg arrivals per time unit: 3.0, Avg CPU burst

time: 2, Context switch time: 0.0]:

Avg. turnaround time: 12.626963581749276

Avg. waiting time: 10.01381260053213

-----

SJF [#Processes: 10, Avg arrivals per time unit: 3.0, Avg CPU burst

time: 2, Context switch time: 0.0]:

Avg. turnaround time: 9.484330868807557

Avg. waiting time: 6.871179887590411

-----

RR [#Processes: 10, Avg arrivals per time unit: 3.0, Avg CPU burst

time: 2, Context switch time: 0.0, Quantum: 0.5]:

Avg. turnaround time: 16.262966521167005

Avg. waiting time: 13.649815539949861

-----

SRTF [#Processes: 10, Avg arrivals per time unit: 3.0, Avg CPU burst

time: 2, Context switch time: 0.0]:

Avg. turnaround time: 9.436993491606682

Avg. waiting time: 6.823842510389535


版权所有:留学生编程辅导网 2020 All Rights Reserved 联系方式:QQ:99515681 微信:codinghelp 电子信箱:99515681@qq.com
免责声明:本站部分内容从网络整理而来,只供参考!如有版权问题可联系本站删除。 站长地图

python代写
微信客服:codinghelp