Question Subsidiaire

On reprend les questions du DM avec avec des temps de service déterministes de durées \(1/\mu_1\) et \(1/\mu_2\).

On simule le fonctionnement de ces deux serveurs.

set.seed(2)

# Server 1
mu1 = 1/1. # Processing rate
E1 = 1. # Energetic consumption while dealing with a task
E0 = 0.5 # Energetic consumption while on standby

# Server 2
mu2 = 1/2. # Processing rate
E2 = 4. # Energetic consumption while dealing with a task
E3 = 0.25 # Energetic consumption while on standby

# Other variables
N = 1000 # Number of tasks
# lambda : Arrival rate

simul = function(N, lambda, mu, e_inst, e_veil, server) {
    Arrival = cumsum(rexp(n = N, rate = lambda))
    Service = rep(N, x = mu)
    Remaining = rep(N, x = NA)
    Completion = rep(N, x = NA)
    Energy = 0
    t = 0 # Current time
    CurrentTask = NA;
    NextArrival = 1;
    
    while (TRUE) {
        dtA = NA; # Time until next arrival
        dtC = NA; # Time until next completion
        
        if(length(Arrival[Arrival>t])>0) {
            dtA = head(Arrival[Arrival>t], n = 1) - t
        }
        if(!is.na(CurrentTask)) {
            dtC = Remaining[CurrentTask]
        }
        if(is.na(dtA) & is.na(dtC)) {
            break;
        } 
        dt = min(dtA,dtC,na.rm=T)
        
        if(!is.na(CurrentTask)) {
            Energy = Energy + e_inst*dt
        }
        else{
            Energy = Energy + e_veil*dt
        }
        
        t = t + dt # Current time updated
        if((NextArrival <=N) & (Arrival[NextArrival] <= t)) {
            # Updating the future task incomming
            Remaining[NextArrival] = Service[NextArrival];
            NextArrival = NextArrival + 1;
        }
        if(!is.na(CurrentTask)) {
            Remaining[CurrentTask] = Remaining[CurrentTask] - dt ;
            if(Remaining[CurrentTask] <= 0) {
                Completion[CurrentTask] = t;
                Remaining[CurrentTask] = NA;
            }
            CurrentTask = NA;
        }
        WaitingList=(1:N)[!is.na(Remaining)];
        if(length(WaitingList)>0) {
            CurrentTask = head(WaitingList,n=1);
        }
    }
    return(data.frame(
        server = server,
        lambda=lambda,
        mu=mu,
        Arrival = Arrival,
        Service = Service,
        Completion = Completion,
        Energy = Energy / t
        ))
}

# Simulation Server 1
dfS1 = data.frame()
for (r in seq(from=.1, to=mu1, by=.01)) {
    dfS1 = rbind(dfS1,simul(N, lambda = r, mu1, E1, E0, 'Server 1'));
}

# Simulation Server 2
dfS2 = data.frame()
for (r in seq(from=.1, to=mu1, by=.01)) {
    dfS2 = rbind(dfS2,simul(N, lambda = r, mu2, E2, E3, 'Server 2'));
}

Question 1 : Comparaison de la performance de \(S_1\) et de \(S_2\)

On trace sur une même figure une estimation de l’espérance du temps de réponse des deux serveurs.

# Tracé Temps de réponse
  # Données
dfS1_2 = dfS1 %>% group_by(lambda, mu, server) %>%
    summarize(Response = mean(Completion - Arrival), 
              Response_se = sd(Completion - Arrival)/sqrt(n()))

dfS2_2 = dfS2 %>% group_by(lambda, mu, server) %>%
    summarize(Response = mean(Completion - Arrival), 
              Response_se = sd(Completion - Arrival)/sqrt(n()))

dfS1_2$server = as.character(dfS1_2$server)
dfS2_2$server = as.character(dfS2_2$server)
dfS_response = rbind(dfS1_2,dfS2_2)
dfS_response$server = as.factor(dfS_response$server)

  # Tracés
ggplot(dfS_response, aes(x=lambda, y=Response, group = server, colour = server)) + geom_line() +
    geom_errorbar(aes(ymin = Response - 2*Response_se,
                      ymax = Response + 2*Response_se)) +
  ggtitle("Temps de réponse moyen en fonction du taux d'arrivée") + theme(legend.position = 'right') + xlab("Taux d'arrivée (λ)") + ylab("Temps de réponse moyen") + scale_color_manual(values=c("#D7102E","#11A4D6"))

On observe que les courbes ne se croisent pas. \(S_2\) est plus performant que \(S_1\) en terme de temps de réponse moyen. En effet, \(S_2\) peut se charger de deux tâches par seconde, et n’est donc jamais surchargé au vu du taux d’arrivée.

On trace sur une même figure une estimation de l’espérance de la consommation énergétique des deux serveurs.

# Tracé Consommation énergétique
  # Données
dfS1_2 = dfS1 %>% group_by(lambda, mu, server) %>%
    summarize(Energy_e = mean(Energy), 
              Energy_se = sd(Energy)/sqrt(n()))

dfS2_2 = dfS2 %>% group_by(lambda, mu, server) %>%
    summarize(Energy_e = mean(Energy), 
              Energy_se = sd(Energy)/sqrt(n()))

dfS1_2$server = as.character(dfS1_2$server)
dfS2_2$server = as.character(dfS2_2$server)
dfS_energy = rbind(dfS1_2, dfS2_2)
dfS_energy$server = as.factor(dfS_energy$server)

  # Tracés
ggplot(dfS_energy, aes(x=lambda, y=Energy_e, group = server, colour = server)) + geom_line() +
    geom_errorbar(aes(ymin = Energy_e - 2*Energy_se,
                      ymax = Energy_e + 2*Energy_se)) +
  ggtitle("Consommation électrique moyenne des serveurs en fonction du taux d'arrivée") + theme(legend.position = 'right') + xlab("Taux d'arrivée (λ)") + ylab("Consommation électrique moyenne")

On cherche à donner une estimation relativement précise (à 0.01 près) de la valeur critique de λ pour laquelle \(S_2\) devient plus intéressant que \(S_1\) sur le plan énergétique.

# Simulation Server 1
dfS1_precise = data.frame()
for (r in seq(from=.15, to=0.22, by=.01)) {
    dfS1_precise = rbind(dfS1_precise,simul(N, lambda = r, mu1, E1, E0, 'Server 1'));
}

# Simulation Server 2
dfS2_precise = data.frame()
for (r in seq(from=.15, to=0.22, by=.01)) {
    dfS2_precise = rbind(dfS2_precise,simul(N, lambda = r, mu2, E2, E3, 'Server 2'));
}

# Tracé des courbes
dfS1_2_precise = dfS1_precise %>% group_by(lambda, mu, server) %>%
    summarize(Energy_e = mean(Energy), 
              Energy_se = sd(Energy)/sqrt(n()))

dfS2_2_precise = dfS2_precise %>% group_by(lambda, mu, server) %>%
    summarize(Energy_e = mean(Energy), 
              Energy_se = sd(Energy)/sqrt(n()))

dfS1_2_precise$server = as.character(dfS1_2_precise$server)
dfS2_2_precise$server = as.character(dfS2_2_precise$server)
dfS_precise = rbind(dfS1_2_precise, dfS2_2_precise)
dfS_precise$server = as.factor(dfS_precise$server)

  # Tracés
ggplot(dfS_precise, aes(x=lambda, y=Energy_e, group = server, colour = server)) + geom_line() + ylim(0.5,0.7) +
    geom_errorbar(aes(ymin = Energy_e - 2*Energy_se,
                      ymax = Energy_e + 2*Energy_se)) +
  ggtitle("Consommation électrique moyenne des serveurs en fonction du taux d'arrivée") + theme(legend.position = 'right') + xlab("Taux d'arrivée (λ)") + ylab("Consommation électrique moyenne") + scale_color_manual(values=c("#D7102E","#11A4D6"))

On observe qu’au delà de \(λ≈0.18\), il est plus rentable sur le plan énergétique d’utiliser \(S_1\) que \(S_2\).

Question 2 : Etude du serveur \(S_3\)

On dispose maintenant d’un serveur \(S_3\) combinant les deux serveurs précédents. Les deux serveurs travaillent en parallèle et lorsqu’un processeur termine de traiter une tâche, il prend la première en attente. La consommation énergétique est la somme de la consommation énergétique des deux serveurs.

On simule le fonctionnement de ce serveur.

simul3 = function(N, lambda, mu1, mu2, e_inst1, e_veil1, e_inst2, e_veil2, server) {
    Arrival = cumsum(rexp(n = N, rate = lambda))
    Service1 = rep(N, x = mu1)
    Service2 = rep(N, x = mu2)
    Remaining1 = rep(N, x = NA)
    Remaining2 = rep(N, x = NA)
    Completion1 = rep(N, x = NA)
    Completion2 = rep(N, x = NA)
    Energy = 0
    t = 0 # Current time
    CurrentTask1 = NA
    CurrentTask2 = NA
    NextArrival = 1
    NextArrival1 = 1
    NextArrival2 = 1
    
    while (TRUE) {
        dtA = NA # Time until next arrival
        dtC1 = NA # Time until next completion on server 1
        dtC2 = NA # Time until next completion on server 2
        
        if(length(Arrival[Arrival>t])>0) {
            dtA = head(Arrival[Arrival>t], n = 1) - t
        }
        if(!is.na(CurrentTask1)) dtC1 = Remaining1[CurrentTask1]
        if(!is.na(CurrentTask2)) dtC2 = Remaining2[CurrentTask2]
        if(is.na(dtA) & is.na(dtC1) & is.na(dtC2)) {
            break;
        } 
        dt = min(dtA,dtC1,dtC2,na.rm=T)
        
        if( !is.na(CurrentTask1) && !is.na(CurrentTask2) ) {
            Energy = Energy + (e_inst1 + e_inst2)*dt
        } else if( !is.na(CurrentTask1) && is.na(CurrentTask2) ) {
            Energy = Energy + (e_inst1 + e_veil2)*dt
        } else if( is.na(CurrentTask1) && !is.na(CurrentTask2) ) {
            Energy = Energy + (e_inst2 + e_veil1)*dt
        } else {
            Energy = Energy + (e_veil1 + e_veil2)*dt
        }
        
        t = t + dt # Current time updated
        if((NextArrival <= N) & (Arrival[NextArrival] <= t)) {
            # Updating the future task incomming
            if(is.na(dtC1) || (!is.na(dtC2) && dtC1 <= dtC2)) {
              Remaining1[NextArrival1] = Service1[NextArrival]
              NextArrival1 = NextArrival1 + 1
            } else {
              Remaining2[NextArrival2] = Service2[NextArrival]
              NextArrival2 = NextArrival2 + 1
            }
            NextArrival = NextArrival + 1
        }
        if(!is.na(CurrentTask1)) {
            Remaining1[CurrentTask1] = Remaining1[CurrentTask1] - dt
            if(Remaining1[CurrentTask1] <= 0) {
                Completion1[CurrentTask1] = t
                Remaining1[CurrentTask1] = NA
            }
            CurrentTask1 = NA
        }
        if(!is.na(CurrentTask2)) {
            Remaining2[CurrentTask2] = Remaining2[CurrentTask2] - dt ;
            if(Remaining2[CurrentTask2] <= 0) {
                Completion2[CurrentTask2] = t
                Remaining2[CurrentTask2] = NA
            }
            CurrentTask2 = NA
        }
        WaitingList=(1:N)[!is.na(Remaining1)]
        if(length(WaitingList)>0) {
            CurrentTask1 = head(WaitingList,n=1)
        }
        WaitingList=(1:N)[!is.na(Remaining2)]
        if(length(WaitingList)>0) {
            CurrentTask2 = head(WaitingList,n=1)
        }
    }
    return(data.frame(
        server = server,
        lambda = lambda,
        Arrival = Arrival,
        Completion1 = Completion1,
        Completion2 = Completion2,
        Energy = Energy / t
        ))
}

# Simulation Server 3
 dfS3 = data.frame()
  for (r in seq(from=.1, to=mu1+mu2, by=.1)) {
     dfS3 = rbind(dfS3,simul3(N, lambda = r, mu1, mu2, E1, E0, E2, E3, 'Server 3'));
 }

On étudie la performance du serveur \(S_3\) en ajoutant cette nouvelle simulation aux deux tracés précédents.

# Tracé Temps de réponse
  # Données
dfS3_2 = dfS3 %>% group_by(lambda, server) %>%
    summarize(Response = mean(sort(rbind(Completion1, Completion2)) - Arrival), 
              Response_se = sd(sort(rbind(Completion1, Completion2)) - Arrival)/sqrt(n()))

dfS3_2$server = as.character(dfS3_2$server)
dfS_response$server = as.character(dfS_response$server)
dfS_response = rbind(dfS_response,dfS3_2)
dfS_response$server = as.factor(dfS_response$server)

  # Tracés
ggplot(dfS_response, aes(x=lambda, y=Response, group = server, colour = server)) + geom_line() +
    geom_errorbar(aes(ymin = Response - 2*Response_se,
                      ymax = Response + 2*Response_se)) +
  ggtitle("Temps de réponse moyen en fonction du taux d'arrivée") + theme(legend.position = 'right') + xlab("Taux d'arrivée (λ)") + ylab("Temps de réponse moyen") + scale_color_manual(values=c("#D7102E","#11A4D6","#1E9F47"))

Sachant que le serveur \(S_3\) utilise les serveurs \(S_1\) et \(S_2\) en parallèle, on peut comprendre qu’il n’est donc pas lui-même surchargé.

# Tracé Consommation énergétique
  # Données
dfS3_2 = dfS3 %>% group_by(lambda, server) %>%
    summarize(Energy_e = mean(Energy), 
              Energy_se = sd(Energy)/sqrt(n()))

dfS3_2$server = as.character(dfS3_2$server)
dfS_energy$server = as.character(dfS_energy$server)
dfS_energy = rbind(dfS_energy, dfS3_2)
dfS_energy$server = as.factor(dfS_energy$server)

  # Tracés
ggplot(dfS_energy, aes(x=lambda, y=Energy_e, group = server, colour = server)) + geom_line() + ylim(0,3) +
    geom_errorbar(aes(ymin = Energy_e - 2*Energy_se,
                      ymax = Energy_e + 2*Energy_se)) +
  ggtitle("Consommation électrique moyenne des serveurs en fonction du taux d'arrivée") + theme(legend.position = 'right') + xlab("Taux d'arrivée (λ)") + ylab("Consommation électrique moyenne") + scale_color_manual(values=c("#D7102E","#11A4D6","#1E9F47"))

De même, on voit que les courbes de \(S_2\) et \(S_3\) ne se croisent pas. Il est cependant, à l’inverse, plus rentable sur le plan énergétique d’utiliser le serveur \(S_2\) par rapport au serveur \(S_3\).