On dispose d’un serveur \(S_1\) : Un serveur un peu ancien, peu puissant, avec un temps de service exponentiel de taux \(μ_1 = 1\), une consommation énergétique instantanée \(E_1 = 1\) lorsqu’il est en train de traiter une tâche et \(E_0 = 0.5\) lorsqu’il est en veille.

On dispose également \(S_2\) : Un serveur moderne, assez puissant, avec un temps de service exponentiel de taux \(μ_2 = 2\), une consommation énergétique instantanée \(E_2 = 4\) lorsqu’il est en train de traiter une tâche et \(E_0 = 0.25\) lorsqu’il est en veille.

On simule le fonctionnement de ces deux serveurs.

set.seed(2)

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

# Server 2
mu2 = 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 = rexp(n = N, rate = 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=.1)) {
    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=mu2, by=.1)) {
    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() + ylim(0,20) +
    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 trace sur une même figure une estimation de l’expé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() + ylim(0,5) +
    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.25,0.75) +
    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 \(λ \approx 0.18\), il est plus rentable 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 = rexp(n = N, rate = mu1)
    Service2 = rexp(n = N, rate = 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() + ylim(0,20) +
    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"))