This code is based on the G/G/1 queues simulation code for different scheduling disciplines, with a few tweaks. It is included to compare against the G/G/k implementation.
# This function plots the average response time of a M/G/k queue for various scheduling disciplines.
# As input, it takes the job service times (Service), their mean value (ES),
# a range of arrival rates for the Poisson process (lambdas), and the number of servers (k),
# as well as optional parameters to toggle logging and plotting.
plot_response_times_GGk <- function(Service, ES, lambdas, k, loglevel=1, plot=TRUE) {
N = length(Service); # number of jobs to simulate;
debug=0;
scheduling_disciplines = c(1:4); # 'FCFS'=1,'LCFS'=2,'RO-S'=3,'SRPT'=4
# Matrix of average response times for all disciplines and arrival rates
R_time <- matrix(0, nrow = length(scheduling_disciplines), ncol = length(lambdas));
rownames(R_time) <- c("FCFS-PR","LCFS-PR","RO-S-PR","SRPT-PR");
colnames(R_time) <- paste("rho=", round(lambdas*ES, digits = 3), sep = "");
cnt=0;
for (lambda in lambdas)
{
cnt=cnt+1;
# Let's assume a Poisson process coming in, otherwise change the next line
Arrival = cumsum(rexp(n=N, rate = lambda)); # Arrival times
if (loglevel>=2){cat("Arrival=", Arrival, "\nService=", Service, "\n")}
for (scheduling_discipline in scheduling_disciplines)
{
if(ONLY_FCFS && scheduling_discipline!= 1) {break;}
if (loglevel>=2){cat("\tSimulating discipline ", rownames(R_time)[scheduling_discipline], "\n")}
t = 0; # simulation time
Remaining = rep(N, x=NA); # Remaining service times of each job
Completion = rep(N, x=NA); # Completion time of each job
AvgJobs = 0;
CurrentTasks = rep(k, x=NA);
NextArrival = 1;
while (TRUE) {
if (loglevel>=3){
print("*********************");
print(cat("Arrival = ", Arrival));
print(cat("Service = ", Service));
print(cat("Remaining = ", Remaining));
}
dtA = NA; # Time to next arrival
dtC = NA; # Time to next completion
if(length(Arrival[Arrival>t])>0) { # if an arrival exists after t
NextArrivalTime = head(Arrival[Arrival>t],n=1) ; # time to next arrival
dtA = NextArrivalTime - t;
}
if(length(CurrentTasks[!is.na(CurrentTasks)]) > 0) { # if a task is running
# time to next completion: min of all remaining times for non-NA current tasks
dtC = min(Remaining[CurrentTasks[!is.na(CurrentTasks)]]);
}
if(is.na(dtA) & is.na(dtC)) {
break; # No event, exit
}
dt = min(dtA,dtC,na.rm=T); # Time to next "event" (arrival or completion)
# Move to next event, update system variables
# t = t + dt; # WARNING: this is not the same as the line below
# Because we use a "==" condition to determine if the simulation time
# has reached the NextArrival time, we need to be extremely careful with
# floating-point precision errors. If we detect that dt is dtA, we avoid
# the addition altogether and set the time to exactly the NextArrival time.
t = ifelse(!is.na(dtA) & dt==dtA, NextArrivalTime, t+dt);
if (is.na(t)) browser()
AvgJobs=AvgJobs+dt*sum(!is.na(Remaining));
# If the event is a new task arrival
if((NextArrival <=N) & (Arrival[NextArrival] == t)) {
# This task's Remaining is its length, because it only just started
Remaining[NextArrival] = Service[NextArrival];
NextArrival = NextArrival + 1; # The next arrival is the next task
}
# Job departure
# for each server that has a running task:
for (i in which(!is.na(CurrentTasks))) {
task = CurrentTasks[i]
# Decrease its Remaining
Remaining[task] = Remaining[task] - dt;
if (Remaining[task] <= 0) {
# If its Remaining hits zero, save completion time and clear task
Completion[task] = t;
Remaining[task] = NA;
CurrentTasks[i] = NA;
}
}
if (loglevel>=2){
cat("Sim. time:", t, "| Jobs arrived:", NextArrival-1, "| Jobs completed: ", length(Completion[!is.na(Completion)]), "\n");
}
# Scheduling
# Our various scheduling disciplines each have 2 versions.
# - preemptive: running tasks are removed from the servers and treated
# just like new tasks, which means they can be stopped or moved
# - non-preemptive: running tasks are excluded from scheduling, they
# run on the same server until completion
# We implement the preemptive version here
CurrentTasks = rep(k, x=NA);
# for each server:
for (i in 1:k) {
# Therefore the WaitingList = all tasks up to NextArrival that exist in Remaining,
# excluding the ones that exist in CurrentTasks
WaitingList=(1:NextArrival)[!is.na(Remaining)];
WaitingList=WaitingList[!(WaitingList %in% CurrentTasks)];
if (length(WaitingList)>0) {
# FCFS
if (scheduling_discipline==1){
CurrentTasks[i] = head(WaitingList,n=1);
}
# LCFS
if (scheduling_discipline==2){
CurrentTasks[i] = tail(WaitingList,n=1);
}
# ROS: Random Order of Service
if (scheduling_discipline==3){
if (length(WaitingList)>1) {
CurrentTasks[i] = sample(WaitingList,size=1);
} else{
CurrentTasks[i] = WaitingList;
}
}
# SRPT: Shortest Remaining Processing Time
if (scheduling_discipline==4){
# Find minimum in Remaining, excluding already scheduled tasks
rem=Remaining;
for (task in CurrentTasks) { rem[task]=NA; }
CurrentTasks[i] = which.min(rem);
}
}
if (loglevel>=2){cat("Current Tasks = ", CurrentTasks, "\n")}
} # end of scheduling
} # end of main loop
ResponseTime = mean(Completion-Arrival); #average response time
AvgJobs=AvgJobs/(tail(Completion,n=1)-Arrival[1]);
# Simulation completed. Let's verify Little law: N=lambda*R
r_names=rownames(R_time)
if (loglevel>=1){
cat(paste("Sim. ",r_names[scheduling_discipline],"(rho=",round(lambda*ES,digits = 2),") completed."));
cat(paste(" Little law verification: ", round(AvgJobs,digits = 4), "=N=lambda*R=", round(lambda*ResponseTime,digits = 4),"\n"));
}
R_time[scheduling_discipline,cnt]=ResponseTime;
} # loop scheduling discipline
} # loop lambda
title = paste("G/G/k,", N, "jobs,", k, "servers")
if (loglevel>=1){
cat("\n", title, "\n")
cat("Response time matrix:\n")
print(R_time)
}
if(plot){
matplot(t(R_time), type = "l", xlab="Load (lambda*E[S])", ylab="Avg Response Time", main=title, xaxt = "n", lwd = 3);
legend("topleft", legend = rownames(R_time), col=1:4, pch=1);
axis(side=1,at=1:ncol(R_time),labels=round(lambdas*ES, digits=3));
}
return(R_time);
} # end function
We take \(k = 3\), the number of servers. \(N = 10\) the number of jobs simulated. \(\mu = 1\) and \(ES = 1/\mu\). The number of arrival rates simulated are \(5\)
Simulate the dynamics induced by 10 arrivals :
## Sim. FCFS-PR (rho= 0.17 ) completed. Little law verification: 0.1668 =N=lambda*R= 0.2391
## Sim. LCFS-NP (rho= 0.17 ) completed. Little law verification: 0.1668 =N=lambda*R= 0.2391
## Sim. RO-S-NP (rho= 0.17 ) completed. Little law verification: 0.1668 =N=lambda*R= 0.2391
## Sim. SRPT-PR (rho= 0.17 ) completed. Little law verification: 0.1668 =N=lambda*R= 0.2391
## Sim. FCFS-PR (rho= 0.33 ) completed. Little law verification: 1.5473 =N=lambda*R= 0.9704
## Sim. LCFS-NP (rho= 0.33 ) completed. Little law verification: 1.5754 =N=lambda*R= 0.988
## Sim. RO-S-NP (rho= 0.33 ) completed. Little law verification: 1.7435 =N=lambda*R= 1.0934
## Sim. SRPT-PR (rho= 0.33 ) completed. Little law verification: 1.2008 =N=lambda*R= 0.7531
## Sim. FCFS-PR (rho= 0.5 ) completed. Little law verification: 2.2738 =N=lambda*R= 1.7888
## Sim. LCFS-NP (rho= 0.5 ) completed. Little law verification: 1.9961 =N=lambda*R= 1.5704
## Sim. RO-S-NP (rho= 0.5 ) completed. Little law verification: 1.6393 =N=lambda*R= 1.2897
## Sim. SRPT-PR (rho= 0.5 ) completed. Little law verification: 1.4584 =N=lambda*R= 1.1474
## Sim. FCFS-PR (rho= 0.67 ) completed. Little law verification: 2.5598 =N=lambda*R= 2.4875
## Sim. LCFS-NP (rho= 0.67 ) completed. Little law verification: 3.3434 =N=lambda*R= 2.5775
## Sim. RO-S-NP (rho= 0.67 ) completed. Little law verification: 2.4744 =N=lambda*R= 2.4045
## Sim. SRPT-PR (rho= 0.67 ) completed. Little law verification: 2.2413 =N=lambda*R= 1.6195
## Sim. FCFS-PR (rho= 0.83 ) completed. Little law verification: 2.09 =N=lambda*R= 2.9305
## Sim. LCFS-NP (rho= 0.83 ) completed. Little law verification: 1.6836 =N=lambda*R= 2.2235
## Sim. RO-S-NP (rho= 0.83 ) completed. Little law verification: 1.5535 =N=lambda*R= 2.1782
## Sim. SRPT-PR (rho= 0.83 ) completed. Little law verification: 1.6339 =N=lambda*R= 1.8656
##
## G/G/1, 10 jobs
## Response time matrix:
## rho=0.167 rho=0.333 rho=0.5 rho=0.667 rho=0.833
## FCFS-PR 1.434482 2.911081 3.577672 3.731247 3.516596
## LCFS-NP 1.434482 2.963926 3.140731 3.866218 2.668167
## RO-S-NP 1.434482 3.280152 2.579390 3.606817 2.613832
## SRPT-PR 1.434482 2.259158 2.294784 2.429253 2.238756
## Sim. FCFS-PR (rho= 0.17 ) completed. Little law verification: 0.1668 =N=lambda*R= 0.2391
## Sim. LCFS-PR (rho= 0.17 ) completed. Little law verification: 0.1668 =N=lambda*R= 0.2391
## Sim. RO-S-PR (rho= 0.17 ) completed. Little law verification: 0.1668 =N=lambda*R= 0.2391
## Sim. SRPT-PR (rho= 0.17 ) completed. Little law verification: 0.1668 =N=lambda*R= 0.2391
## Sim. FCFS-PR (rho= 0.33 ) completed. Little law verification: 0.7997 =N=lambda*R= 0.4782
## Sim. LCFS-PR (rho= 0.33 ) completed. Little law verification: 0.7997 =N=lambda*R= 0.4782
## Sim. RO-S-PR (rho= 0.33 ) completed. Little law verification: 0.7997 =N=lambda*R= 0.4782
## Sim. SRPT-PR (rho= 0.33 ) completed. Little law verification: 0.7997 =N=lambda*R= 0.4782
## Sim. FCFS-PR (rho= 0.5 ) completed. Little law verification: 1.0786 =N=lambda*R= 0.7172
## Sim. LCFS-PR (rho= 0.5 ) completed. Little law verification: 1.0786 =N=lambda*R= 0.7172
## Sim. RO-S-PR (rho= 0.5 ) completed. Little law verification: 1.0786 =N=lambda*R= 0.7172
## Sim. SRPT-PR (rho= 0.5 ) completed. Little law verification: 1.0786 =N=lambda*R= 0.7172
## Sim. FCFS-PR (rho= 0.67 ) completed. Little law verification: 1.1514 =N=lambda*R= 0.9563
## Sim. LCFS-PR (rho= 0.67 ) completed. Little law verification: 1.1514 =N=lambda*R= 0.9563
## Sim. RO-S-PR (rho= 0.67 ) completed. Little law verification: 1.1514 =N=lambda*R= 0.9563
## Sim. SRPT-PR (rho= 0.67 ) completed. Little law verification: 1.1514 =N=lambda*R= 0.9563
## Sim. FCFS-PR (rho= 0.83 ) completed. Little law verification: 1.6653 =N=lambda*R= 1.2312
## Sim. LCFS-PR (rho= 0.83 ) completed. Little law verification: 1.7076 =N=lambda*R= 1.2312
## Sim. RO-S-PR (rho= 0.83 ) completed. Little law verification: 1.7318 =N=lambda*R= 1.2487
## Sim. SRPT-PR (rho= 0.83 ) completed. Little law verification: 1.7076 =N=lambda*R= 1.2312
##
## G/G/k, 10 jobs, 3 servers
## Response time matrix:
## rho=0.167 rho=0.333 rho=0.5 rho=0.667 rho=0.833
## FCFS-PR 1.434482 1.434482 1.434482 1.434482 1.477436
## LCFS-PR 1.434482 1.434482 1.434482 1.434482 1.477436
## RO-S-PR 1.434482 1.434482 1.434482 1.434482 1.498382
## SRPT-PR 1.434482 1.434482 1.434482 1.434482 1.477436
## rho=0.167 rho=0.333 rho=0.5 rho=0.667 rho=0.833
## GG1 FCFS-PR 1.4344816 2.9110814 3.5776721 3.7312468 3.5165955
## GGk FCFS-PR 1.4344816 1.4344816 1.4344816 1.4344816 1.4774364
## Ratio GGk/GG1 1.0000000 0.4927659 0.4009539 0.3844510 0.4201326
##
## GG1 LCFS-NP 1.4344816 2.9639255 3.1407308 3.8662179 2.6681668
## GGk LCFS-PR 1.4344816 1.4344816 1.4344816 1.4344816 1.4774364
## Ratio GGk/GG1 1.0000000 0.4839803 0.4567350 0.3710297 0.5537272
##
## GG1 RO-S-NP 1.4344816 3.2801524 2.5793904 3.6068167 2.6138321
## GGk RO-S-PR 1.4344816 1.4344816 1.4344816 1.4344816 1.4983817
## Ratio GGk/GG1 1.0000000 0.4373216 0.5561320 0.3977140 0.5732509
##
## GG1 SRPT-PR 1.4344816 2.2591580 2.2947841 2.4292533 2.2387560
## GGk SRPT-PR 1.4344816 1.4344816 1.4344816 1.4344816 1.4774364
## Ratio GGk/GG1 1.0000000 0.6349629 0.6251052 0.5905031 0.6599363
As we can see with the plots, the results are not consistent and the Little’s law is not well calibrated, this is due to the low amount of jobs provided to the system. With \(N=1000\) we can verify that the code is indeed correct.
## Sim. FCFS-PR (rho= 0.17 ) completed. Little law verification: 0.1918 =N=lambda*R= 0.207
## Sim. LCFS-NP (rho= 0.17 ) completed. Little law verification: 0.1919 =N=lambda*R= 0.2072
## Sim. RO-S-NP (rho= 0.17 ) completed. Little law verification: 0.1918 =N=lambda*R= 0.2071
## Sim. SRPT-PR (rho= 0.17 ) completed. Little law verification: 0.1704 =N=lambda*R= 0.1839
## Sim. FCFS-PR (rho= 0.33 ) completed. Little law verification: 0.5491 =N=lambda*R= 0.5473
## Sim. LCFS-NP (rho= 0.33 ) completed. Little law verification: 0.533 =N=lambda*R= 0.5312
## Sim. RO-S-NP (rho= 0.33 ) completed. Little law verification: 0.5443 =N=lambda*R= 0.5425
## Sim. SRPT-PR (rho= 0.33 ) completed. Little law verification: 0.446 =N=lambda*R= 0.4446
## Sim. FCFS-PR (rho= 0.5 ) completed. Little law verification: 1.3485 =N=lambda*R= 1.3864
## Sim. LCFS-NP (rho= 0.5 ) completed. Little law verification: 1.4171 =N=lambda*R= 1.457
## Sim. RO-S-NP (rho= 0.5 ) completed. Little law verification: 1.3201 =N=lambda*R= 1.3572
## Sim. SRPT-PR (rho= 0.5 ) completed. Little law verification: 0.8411 =N=lambda*R= 0.8647
## Sim. FCFS-PR (rho= 0.67 ) completed. Little law verification: 2.1744 =N=lambda*R= 2.2312
## Sim. LCFS-NP (rho= 0.67 ) completed. Little law verification: 2.1818 =N=lambda*R= 2.2388
## Sim. RO-S-NP (rho= 0.67 ) completed. Little law verification: 2.1905 =N=lambda*R= 2.2478
## Sim. SRPT-PR (rho= 0.67 ) completed. Little law verification: 1.2651 =N=lambda*R= 1.2981
## Sim. FCFS-PR (rho= 0.83 ) completed. Little law verification: 13.6781 =N=lambda*R= 13.0284
## Sim. LCFS-NP (rho= 0.83 ) completed. Little law verification: 13.7431 =N=lambda*R= 13.0787
## Sim. RO-S-NP (rho= 0.83 ) completed. Little law verification: 13.0694 =N=lambda*R= 12.4393
## Sim. SRPT-PR (rho= 0.83 ) completed. Little law verification: 4.4586 =N=lambda*R= 4.2415
##
## G/G/1, 1000 jobs
## Response time matrix:
## rho=0.167 rho=0.333 rho=0.5 rho=0.667 rho=0.833
## FCFS-PR 1.242160 1.641848 2.772808 3.346814 15.634131
## LCFS-NP 1.243027 1.593652 2.913908 3.358245 15.694438
## RO-S-NP 1.242306 1.627561 2.714479 3.371626 14.927181
## SRPT-PR 1.103618 1.333719 1.729468 1.947204 5.089855
## Sim. FCFS-PR (rho= 0.17 ) completed. Little law verification: 0.1667 =N=lambda*R= 0.1799
## Sim. LCFS-PR (rho= 0.17 ) completed. Little law verification: 0.1667 =N=lambda*R= 0.1799
## Sim. RO-S-PR (rho= 0.17 ) completed. Little law verification: 0.1667 =N=lambda*R= 0.1799
## Sim. SRPT-PR (rho= 0.17 ) completed. Little law verification: 0.1667 =N=lambda*R= 0.1799
## Sim. FCFS-PR (rho= 0.33 ) completed. Little law verification: 0.3655 =N=lambda*R= 0.3611
## Sim. LCFS-PR (rho= 0.33 ) completed. Little law verification: 0.3649 =N=lambda*R= 0.3604
## Sim. RO-S-PR (rho= 0.33 ) completed. Little law verification: 0.3647 =N=lambda*R= 0.3603
## Sim. SRPT-PR (rho= 0.33 ) completed. Little law verification: 0.3646 =N=lambda*R= 0.3602
## Sim. FCFS-PR (rho= 0.5 ) completed. Little law verification: 0.5319 =N=lambda*R= 0.5465
## Sim. LCFS-PR (rho= 0.5 ) completed. Little law verification: 0.5313 =N=lambda*R= 0.546
## Sim. RO-S-PR (rho= 0.5 ) completed. Little law verification: 0.5319 =N=lambda*R= 0.5466
## Sim. SRPT-PR (rho= 0.5 ) completed. Little law verification: 0.5295 =N=lambda*R= 0.5441
## Sim. FCFS-PR (rho= 0.67 ) completed. Little law verification: 0.7529 =N=lambda*R= 0.7311
## Sim. LCFS-PR (rho= 0.67 ) completed. Little law verification: 0.7505 =N=lambda*R= 0.7287
## Sim. RO-S-PR (rho= 0.67 ) completed. Little law verification: 0.7485 =N=lambda*R= 0.7268
## Sim. SRPT-PR (rho= 0.67 ) completed. Little law verification: 0.7467 =N=lambda*R= 0.7251
## Sim. FCFS-PR (rho= 0.83 ) completed. Little law verification: 0.9327 =N=lambda*R= 0.923
## Sim. LCFS-PR (rho= 0.83 ) completed. Little law verification: 0.9349 =N=lambda*R= 0.9252
## Sim. RO-S-PR (rho= 0.83 ) completed. Little law verification: 0.9318 =N=lambda*R= 0.9221
## Sim. SRPT-PR (rho= 0.83 ) completed. Little law verification: 0.9231 =N=lambda*R= 0.9135
##
## G/G/k, 1000 jobs, 3 servers
## Response time matrix:
## rho=0.167 rho=0.333 rho=0.5 rho=0.667 rho=0.833
## FCFS-PR 1.079463 1.083207 1.093083 1.096673 1.107595
## LCFS-PR 1.079463 1.081345 1.092029 1.093104 1.110234
## RO-S-PR 1.079463 1.080848 1.093158 1.090180 1.106474
## SRPT-PR 1.079463 1.080460 1.088168 1.087632 1.096231
## rho=0.167 rho=0.333 rho=0.5 rho=0.667 rho=0.833
## GG1 FCFS-PR 1.2421603 1.6418475 2.7728080 3.3468142 15.6341313
## GGk FCFS-PR 1.0794629 1.0832069 1.0930828 1.0966731 1.1075955
## Ratio GGk/GG1 0.8690206 0.6597487 0.3942151 0.3276767 0.0708447
##
## GG1 LCFS-NP 1.2430271 1.5936524 2.9139081 3.3582448 15.6944382
## GGk LCFS-PR 1.0794629 1.0813451 1.0920289 1.0931036 1.1102345
## Ratio GGk/GG1 0.8684146 0.6785326 0.3747644 0.3254985 0.0707406
##
## GG1 RO-S-NP 1.2423062 1.6275614 2.7144791 3.3716260 14.9271812
## GGk RO-S-PR 1.0794629 1.0808482 1.0931580 1.0901798 1.1064735
## Ratio GGk/GG1 0.8689185 0.6640906 0.4027137 0.3233395 0.0741247
##
## GG1 SRPT-PR 1.1036184 1.3337190 1.7294677 1.9472043 5.0898548
## GGk SRPT-PR 1.0794629 1.0804596 1.0881678 1.0876324 1.0962309
## Ratio GGk/GG1 0.9781124 0.8101104 0.6291923 0.5585610 0.2153757
We want to test the theory and the simulation, to do this we make these asumptions
If the traffic is greater than 1 then the queue will grow without bound but if server utilization \(\rho = \frac{\lambda} {k * \mu} < 1\) then the system has a stationary distribution with a probability mass function.
For our theoretical calculations we will use the Erlang’s C formula, namely an implementation found on Github
\[ {\displaystyle {\text{ C}}(c,\lambda /\mu )={\frac {1}{1+\left(1-\rho \right)\left({\frac {c!}{(c\rho )^{c}}}\right)\sum _{k=0}^{c-1}{\frac {(c\rho )^{k}}{k!}}}}} \] The response time of our system can now be calculated with the Response time formula
\[ {\displaystyle {\frac {{\text{ C}}(c,\lambda /\mu )}{c\mu -\lambda }}+{\frac {1}{\mu }}.} \]
# This function calculates the Erlang B formula, which is used to find the probability of all servers being busy in a given time.
erlang_b <- function(number_of_servers, intensity) {
inverse_b <- 1.0
# Loop through each server
for(i in 1:number_of_servers) {
inverse_b <- 1.0 + inverse_b * i / intensity
}
# Return the inverse of the final calculation, which gives us the Erlang B formula
return(1.0 / inverse_b)
}
# This function calculates the Erlang C formula, which is used to find the number of calls that are blocked or delayed.
erlang_c <- function(number_of_agents, intensity) {
# Calculate Erlang C using the Erlang B formula
ec <- number_of_agents * erlang_b(number_of_agents, intensity) / (number_of_agents - intensity * (1 - erlang_b(number_of_agents, intensity)))
return(ec)
}
# Define the number of servers
K_values = seq(1, 3, by = 1);
# Define the number of jobs
N = 10;
# Set ONLY_FCFS to TRUE to only use First Come First Serve discipline
ONLY_FCFS = TRUE;
# Define the number of arrival rates
num_arrival_rates=1;
# Initialize a 2D matrix to store f_rtm_ggk, Wr, and error ratio
results_matrix <- matrix(0, num_arrival_rates * length(K_values), 3);
# Initialize index
index <- 0;
# Loop through each number of servers
for(j in seq_along(K_values)) {
# Set the number of servers
K=K_values[j];
# Set the service rate
mu = K;
# Calculate the expected service time
ES = 1/mu;
# Initialize matrices to store f_rtm_ggk and Wr
f_rtm_ggk <- matrix(0, num_arrival_rates, length(K_values));
Wr_matrix <- matrix(0, num_arrival_rates, length(K_values))
# Calculate the arrival rates
lambdas_a = ((1/ES) * c(1:num_arrival_rates)/(num_arrival_rates+1));
# Set the seed for reproducibility
set.seed(global_seed);
# Generate service times
Service = rexp(N,rate=mu);
# Calculate the response times for the G/G/k queue
set.seed(global_seed);
rtm_ggk = plot_response_times_GGk(Service, ES, lambdas_a, K, loglevel=0, plot=FALSE);
f_rtm_ggk[,j] <- rtm_ggk[1, , drop = TRUE];
# Loop through each arrival rate
for(i in 1:num_arrival_rates){
# Set the arrival rate
lambda = lambdas_a[i];
# Calculate the traffic intensity
p <- lambda / (K * mu);
# Check if system is stable
if (p >= 1) {
cat("System is not stable for arrival rate", lambda, "\n")
Wr = Inf
} else {
# Calculate the probability of having K customers in the system using Erlang-C formula
C_K = erlang_c(K, p);
# Calculate average number of customers in the queue using Little's law
Lq = p * C_K / (1 - p)
# Calculate average time spent in the queue using Little's law
Wq = Lq / lambda
# Calculate average service time
Ws = 1 / mu
# Calculate average response time as sum of average time spent in the queue and average service time
Wr = Wq + Ws
Wr_matrix[i, j] <- Wr
}
# Increment index
index <- index + 1;
# Store the results
results_matrix[index, 1] = f_rtm_ggk[i,j];
results_matrix[index, 2] = Wr;
results_matrix[index, 3] = abs(f_rtm_ggk[i,j] - Wr) /Wr;
}
}
# Create a vector of indices for the x-axis
indices <- 1:nrow(results_matrix)
# Create a matrix with f_rtm_ggk and Wr columns
plot_matrix <- cbind(results_matrix[, 1], results_matrix[, 2])
# Create a vector of colors for each row
colors <- c("red", "blue")
# Plot the results matrix
title = paste("Response time comparison for variating \n number of servers and ", N, "jobs")
matplot(indices, plot_matrix, type = "l", lty = 1, col = colors, xlab = "Number of servers", ylab = "Response Times", main = title)
# Add a legend to the plot
legend("topright", legend = c("Sim", "Formulas"), col = colors, lty = 1, title = "Response Time Values")
For 10 jobs and K ∈ {1,2,3} we can see that the theory and the real simulation are still a bit far, but with more jobs, we directly see that our calculations are correct.
For increasing values of the load (i.e., arrival rate divided by service rate) and k = 5, we plot the ratio between the average response time obtained by a G/G/k/FCFS queue and the average response time obtained by a G/G/1/FCFS queue with a server that operates at speed k.
N = 1e3;
K = 5;
mu = 1;
ES_GG1 = 1/mu;
ES_GGK = 1/mu;
num_arrival_rates=5;
set.seed(global_seed);
ServiceGGK = rexp(N,rate=mu);
ServiceGG1 = ServiceGGK/K # All jobs are just K times shorter
Trials = 6;
Factor = 2;
# Initialize 3D arrays
f_rtm_gg1 <- matrix(0, Trials, num_arrival_rates);
f_rtm_ggk <- matrix(0, Trials, num_arrival_rates);
ONLY_FCFS = TRUE;
for(i in 1:Trials){
# On simule pour plusieurs espérances différentes de taux d'arrivée
lambdas_gg1 = ((1/ES_GG1) * c(1:num_arrival_rates)/(num_arrival_rates+1)) * (i*Factor);
lambdas_ggk = ((1/ES_GGK) * c(1:num_arrival_rates)/(num_arrival_rates+1)) * (i*Factor);
set.seed(global_seed)
rtm_gg1 <- plot_response_times_GG1(ServiceGG1, ES_GG1, lambdas_gg1, loglevel=0, plot=FALSE)
set.seed(global_seed)
rtm_ggk <- plot_response_times_GGk(ServiceGGK, ES_GGK, lambdas_ggk, K, loglevel=0, plot=FALSE)
# Store the first row of rtm_gg1 and rtm_ggk in the ith layer of the 3D array
f_rtm_gg1[i,] <- rtm_gg1[1, , drop = FALSE]
f_rtm_ggk[i,] <- rtm_ggk[1, , drop = FALSE]
# Create a matrix with f_rtm_gg1 and f_rtm_ggk stacked vertically for this trial
f_rtm <- rbind(f_rtm_gg1[i,], f_rtm_ggk[i,])
# Create a vector of colors for this trial
colors <- c("red", "blue")
# Create a vector of line types for this trial
lty <- c(1, 2)
# Plot f_rtm using matplot for this trial
matplot(t(f_rtm), type = "l", lty = lty, col = colors, xlab = "Arrival rate", ylab = "Response time", xaxt = "n")
# Add a legend
legend("topright", c("GG1", "GGk"), col = c("red", "blue"), lty = c(1, 2))
axis(side=1,at=1:ncol(f_rtm),labels=round(lambdas_gg1*ES_GG1, digits=2));
# Add a title to the plot with the trial number
title(main = paste("Trial", i))
}
The result show us that the 2 policies are coming closer together as the load increases. This shows that even though the G/G/1 queue is running K times faster than the G/G/K one, when the load increases there is a limit and response times for both are converging to \(1\).
The GG1 model is a single-server queueing model, while the GGK model is a multi-server queueing model with k servers. The main difference between the two models is the number of servers. In the GG1 model, there is only one server, so customers must wait in line if the server is busy. In the GGK model, there are k servers, so customers can be served by any available server. This means that the GGK model can handle a higher load of customers than the GG1 model, as there are more servers available to serve customers.
## [,1] [,2] [,3] [,4] [,5]
## [1,] 0.2670567 0.3114101 0.3467650 0.3715889 0.4461399
## [2,] 0.2876802 0.3905408 0.4867681 0.6145179 0.8075711
## [3,] 0.3115170 0.5279784 0.6924491 0.9799191 0.9921905
## [4,] 0.3355186 0.6606590 0.9833863 0.9929205 0.9979455
## [5,] 0.3601067 0.8220261 0.9951935 0.9988562 0.9988027
## [6,] 0.3836634 0.9809875 0.9953320 0.9993364 0.9990856
We can see thanks to the graph that when the Arrival rate augments, the difference between the G/G/1 queue and the G/G/C queue is lowering untill reaching a ratio of \(1\).
This part seems a bit odd at first but the “overall performance” of each system are the same. Although we will find the system empty more often in the G/G/1 queue but the wait time and expected queue length are less for the G/G/K.
This can be shown with the following formulas. Take \(G/G/1\) and \(G/G/2\) with \(\mu_1 = 2\mu_2\)
\[ \rho_2 = \frac{\lambda}{2\mu_2} = \rho_1 = \frac {\lambda}{\mu_1} \] Now for the G/G/1 queue we find \[ L_q = \frac {\rho²}{1-\rho} ; W_q = \frac {\rho²}{\lambda(1-\rho)} ; \pi_0 = 1 - \rho \] G/G/2 \[ L_q = \frac {\rho³}{1-\rho²} ; W_q = \frac {\rho³}{\lambda(1-\rho²)} ; \pi_0 = \frac {1 - \rho}{1+\rho} \] For a understanding of the utilization, we have twice as many servers, each working half as fast as the alone server, so the total amount being done is the same.
For a general M/M/n queue the utiliztion is \[ \rho_k=\frac{\lambda}{k\mu} \]
We plotted the theoretical values for the G/G/1 and G/G/K queue and spotted the moment when the G/G/1 server response time is higher than the G/G/K queue.
As we can see on the plot, we can find the point where the two curves touch each other
# Define parameters
K <- 5
mu <- K
mu_1 <- K * mu
# Define functions for response times
Wr_5 <- function(lambda) {
p <- lambda / (K * mu)
if (p >= 1) return(Inf)
C_K <- erlang_c(K, p)
Lq <- p * C_K / (1 - p)
Wq <- Lq / lambda
Ws <- 1 / mu
Wr <- Wq + Ws
return(Wr)
}
Wr_1 <- function(lambda) {
p_1 <- lambda / mu_1
RespTime <- p_1 / (mu_1 - lambda)
return(RespTime)
}
# Define range of lambda values
# The range should be up to the capacity of the system, not beyond it
lambda_range <- seq(0, min(K * mu, mu_1), length.out = 1000)
# Calculate response times for each lambda value
Wr_5_values <- sapply(lambda_range, Wr_5)
Wr_1_values <- sapply(lambda_range, Wr_1)
# Remove infinite values and keep only the common indices
valid_indices <- !is.infinite(Wr_5_values) & !is.infinite(Wr_1_values)
Wr_5_values <- Wr_5_values[valid_indices]
Wr_1_values <- Wr_1_values[valid_indices]
lambda_range <- lambda_range[valid_indices]
filter=1;
# Filter response times higher than 5
Wr_5_values_filtered <- Wr_5_values[Wr_5_values < filter]
Wr_1_values_filtered <- Wr_1_values[Wr_1_values < filter]
# Get corresponding lambda values
lambda_range_filtered_5 <- lambda_range[Wr_5_values < filter]
lambda_range_filtered_1 <- lambda_range[Wr_1_values < filter]
# Plot filtered response times
plot(lambda_range_filtered_1, Wr_1_values_filtered, type = "l", xlab = "Arrival rate (lambda)", ylab = "Response time", col = "blue")
lines(lambda_range_filtered_5, Wr_5_values_filtered, type = "l", col = "red")
legend("topright", c("K = 5", "K = 1 (faster server)", "GGK sim", "GG1 sim"), col = c("red", "blue", "purple", "green"), lty = 1)
K=5;
set.seed(global_seed);
ServiceGGK = rexp(N,rate=mu);
ServiceGG1 = ServiceGGK/K;
ES = 1/mu;
f_rtm_ggk <- matrix(0, 1, 1);
f_rtm_gg1 <- matrix(0, 1, 1);
set.seed(global_seed);
rtm_ggk = plot_response_times_GGk(ServiceGGK, ES, 21, K, loglevel=0, plot=FALSE);
f_rtm_ggk[1,] <- rtm_ggk[1, , drop = FALSE]
set.seed(global_seed);
rtm_gg1 = plot_response_times_GG1(ServiceGG1, ES, 24, loglevel=0, plot=FALSE);
f_rtm_gg1[1,] <- rtm_gg1[1, , drop = FALSE]
abline(h = f_rtm_ggk[1,], col="purple", ylab="GGK");
abline(h = f_rtm_gg1[1,], col="green", ylab="GG1");
abline(v = 21, col="black");
axis(side=1,at=21, labels="λ*");
Sources : - Github Erlang’s formula - M/M/K queues formulas