Introduction :

Le problème qui nous est donné est le suivant : est-il raisonnable d’acheter systématiquement la machine la plus puissante possbile, sachant qu’elle coûtera certainement plus cher à l’achat et également plus cher en électricité sur le long terme ?

Pour répondre à ce problème il nous est proposé trois serveurs avec des caractéristiques différentes. Nous devons donc analyser les performances de ces serveurs, modélisés par une file d’attente simple de type M/M/1 en mode FIFO en fonction du taux d’inter-arrivées λ. Pour cela nous devons observer l’espérance du temps de réponse R des tâches et l’espérance de la consommation énergétique E instantanée du serveur.

Nous avons les trois serveurs suivants :

\(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 E1 = 1 lorsqu’il est en train de traiter une tâche et E0 = 0.5 lorsqu’il est en veille.

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

\(S_3\) : Un serveur 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.

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

simul_serveur = function(N=100, arrival_rate=.2, processing_rate=1, consProd = 1, consVeille = 0.3, mode = 1, debug=FALSE) {
    Arrival = cumsum(rexp(n=N, rate=arrival_rate));  
    
    if(mode == 1){
      Service = rexp(n=N, rate =processing_rate); # rep(N,x=1); rgamma(N, shape=.1,rate=.1)
    }
    else{
      Service = rep(N, x = (1/processing_rate));
    }
    
    Remaining = rep(N, x=NA);
    Completion = rep(N, x=NA);
    t = 0;
    Consommation = 0;
    CurrentTask = NA;
    NextArrival = 1;
    while (TRUE) {
        if(debug) print(t);
        if(debug) print(Arrival);
        if(debug) print(Service);
        if(debug) print(Remaining);
        dtA = NA;
        dtC = NA;
        if(length(Arrival[Arrival>t])>0) {
            dtA = head(Arrival[Arrival>t],n=1)-t  # temps jusqu'à la prochaine arrivée
        }
        if(!is.na(CurrentTask)) {
            dtC = Remaining[CurrentTask]; # temps jusqu'à la prochaine terminaison
        }
        if(is.na(dtA) & is.na(dtC)) {
            break;
        } 
        dt = min(dtA,dtC,na.rm=T)
        
        # Mettre à jour comme il faut:
        #   la date
        t = t + dt;
        #   les arrivées
        if((NextArrival <=N) & (Arrival[NextArrival] <= t)) { ## je met un <= et pas un == car 3-2.9!=0.1 ...
            Remaining[NextArrival] = Service[NextArrival];
            NextArrival = NextArrival + 1;
        }
        #   le remaining 
        if(!is.na(CurrentTask)) {
            Consommation = Consommation + consProd*dt;
            Remaining[CurrentTask] = Remaining[CurrentTask] - dt ;
            if(Remaining[CurrentTask] <= 0) {
                Completion[CurrentTask] = t;
                Remaining[CurrentTask] = NA;
            }
            CurrentTask = NA;
        }
        else{
          Consommation = Consommation + consVeille*dt;
        }
        #   et currentTask (politique d'ordonnancement: FIFO)
        WaitingList=(1:N)[!is.na(Remaining)];
        if(length(WaitingList)>0) {
            CurrentTask = head(WaitingList,n=1);
        }
    }
    if(debug) print(Completion)
    return(data.frame(
        Arrival_rate=arrival_rate,
        Arrival = Arrival,
        Completion = Completion,
        Comsumption = Consommation / t
        ))
        
}

# Set la seed pour toujours effectuer les mêmes tirages
set.seed(1)
  
df = data.frame()
for (r in  seq(from=.1, to=.9, by=.1)) {
  df = rbind(df, data.frame(simul_serveur(N = 2000, arrival_rate = r, processing_rate = 1, consProd = 1, consVeille = 0.5, mode = 1, debug = FALSE), Serveur = "S1"));
}

for (r in seq(from=.1, to=.9, by=.1)) {
  df = rbind(df, data.frame(simul_serveur(N = 2000, arrival_rate = r, processing_rate = 2, consProd = 4, consVeille = .25, mode = 1, debug = FALSE), Serveur = "S2")); 
}

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
df2 = df %>% group_by(Arrival_rate, Serveur) %>%
    summarize(Response = mean(Completion - Arrival), 
              Response_se = sd(Completion - Arrival)/sqrt(n()),
              Comsumption = mean(Comsumption),
              Comsumption_se = sd(Comsumption)/sqrt(n()))

library(ggplot2)

ggplot(df2, aes(x=Arrival_rate, y=Response, group = Serveur, color = Serveur)) + geom_line() + theme_bw() + 
    geom_errorbar(aes(ymin = Response - 2*Response_se,
                      ymax = Response + 2*Response_se))

ggplot(df2, aes(x=Arrival_rate, y=Comsumption, group=Serveur, color=Serveur)) + geom_line() + theme_bw()

ggplot(df2, aes(x=Arrival_rate, y=Comsumption, group=Serveur, color=Serveur)) + geom_line() + theme_bw() + coord_cartesian(xlim = c(.14, .2), ylim = c(.5, .75))

Nous pouvons voir avec le graphique ci-dessus que le croisement entre les deux courbes s’effectue vers un λ à peu près égal à 0.18. Ainsi, pour des valeurs de λ inférieures à 0.18, il est plus intéressant sur le plan énergétique d’utiliser le serveur 2 et pour des valeurs supérieures à 0.18 il devient nettement plus intéressant sur le plan énergétique d’utiliser le serveur 1.

Cela semble plutôt logique car avec un λ assez faible le serveur 2 sera très souvent en veille et donc consommera deux fois moins d’énergie que le serveur 1 en étant en veille. Alors que lorsque λ devient plus élevé et que les tâches arrivent de manière plus rapprochée, le serveur 2, travaillera plus de manière continu. Ainsi, ayant une consommation plus élevée en fonctionnement que le serveur 1, sa consommation énergéntique total, sera donc plus élevée que le serveur 1.

Question 2: Etude du serveur \(S_3\)

simul_serveur3 = function(N=100, arrival_rate=.2, processing_rate1=1, consProd1 = 1, consVeille1 = 0.5, processing_rate2=2, consProd2 = 4, consVeille2 = 0.25, mode = 1, debug=FALSE) {
    Arrival = cumsum(rexp(n = N, rate = arrival_rate));  
    
    if(mode == 1){
      Service1 = rexp(n=N, rate = processing_rate1); # rep(N,x=1); rgamma(N, shape=.1,rate=.1)
      Service2 = rexp(n=N, rate = processing_rate2);
    }
    else{
      Service1 = rep(N, x = (1/processing_rate1));
      Service2 = rep(N, x = (1/processing_rate2));
    }
    
    Remaining1 = rep(N, x=NA);
    Remaining2 = rep(N, x = NA);
    Completion = rep(N, x=NA);
    t = 0;
    Consommation = 0;
    CurrentTask1 = NA;
    CurrentTask2 = NA;
    NextArrival = 1;
    while (TRUE) {
        if(debug) print(t);
        if(debug) print(Arrival);
        if(debug) print(Service);
        if(debug) print(Remaining);
        dtA = NA;
        dtC1 = NA;
        dtC2 = NA;
        if(length(Arrival[Arrival>t])>0) {
            dtA = head(Arrival[Arrival>t],n=1)-t  # temps jusqu'à la prochaine arrivée
        }
        if(!is.na(CurrentTask1)) {
            dtC1 = Remaining1[CurrentTask1]; # temps jusqu'à la prochaine terminaison du serveur 1
        }
        if(!is.na(CurrentTask2)) {
            dtC2 = Remaining2[CurrentTask2]; # temps jusqu'à la prochaine terminaison du serveur 2
        }
        if(is.na(dtA) & is.na(dtC1) & is.na(dtC2)) {
            break;
        } 
        dt = min(dtA,dtC1,dtC2,na.rm=T)
        
        # Mettre à jour comme il faut:
        #   la date
        t = t + dt;
        
        #   les arrivées
        if((NextArrival <= N) & (Arrival[NextArrival] <= t)) { ## je met un <= et pas un == car 3-2.9!=0.1 ...
            Remaining1[NextArrival] = Service1[NextArrival];
            Remaining2[NextArrival] = Service2[NextArrival];
            NextArrival = NextArrival + 1;
        }
        
        #   le remaining 
        if(!is.na(CurrentTask1)) {
            Consommation = Consommation + consProd1 * dt;
            Remaining1[CurrentTask1] = Remaining1[CurrentTask1] - dt ;
            if(Remaining1[CurrentTask1] <= 0) {
                Completion[CurrentTask1] = t;
                Remaining1[CurrentTask1] = NA;
            }
            CurrentTask1 = NA;
        }
        else{
          Consommation = Consommation + consVeille1 * dt;
        }
        
        if(!is.na(CurrentTask2)) {
            Consommation = Consommation + consProd2 * dt;
            Remaining2[CurrentTask2] = Remaining2[CurrentTask2] - dt ;
            if(Remaining2[CurrentTask2] <= 0) {
                Completion[CurrentTask2] = t;
                Remaining2[CurrentTask2] = NA;
            }
            CurrentTask2 = NA;
        }
        else{
          Consommation = Consommation + consVeille2 * dt;
        }
        
        #   et currentTask (politique d'ordonnancement: FIFO)
        WaitingList1=(1:N)[!is.na(Remaining1)];
        if(length(WaitingList1)>0) {
            CurrentTask1 = head(WaitingList1,n=1);
            Remaining2[CurrentTask1] = NA;
        }
        
        WaitingList2 = (1:N)[!is.na(Remaining2)];
        if(length(WaitingList2)>0) {
            CurrentTask2 = head(WaitingList2,n=1);
            Remaining1[CurrentTask2] = NA;
        }
    }
    if(debug) print(Completion)
    return(data.frame(
        Arrival_rate = arrival_rate,
        Arrival = Arrival,
        Completion = Completion,
        Comsumption = Consommation / t
        ))
        
}

# Set la seed pour toujours effectuer les mêmes tirages
set.seed(1)
  
df = data.frame()
for (r in  seq(from=.1, to=.9, by=.1)) {
  df = rbind(df, data.frame(simul_serveur(N = 2000, arrival_rate = r, processing_rate = 1, consProd = 1, consVeille = 0.5, mode = 1, debug = FALSE), Serveur = "S1"));
}

for (r in seq(from=.1, to=.9, by=.1)) {
  df = rbind(df, data.frame(simul_serveur(N = 2000, arrival_rate = r, processing_rate = 2, consProd = 4, consVeille = .25, mode = 1, debug = FALSE), Serveur = "S2")); 
}

for (r in  seq(from=.1, to=.9, by=.1)) {
  df = rbind(df, data.frame(simul_serveur3(N=2000, arrival_rate=r, processing_rate1 = 1, consProd1 = 1, consVeille1 = 0.5, processing_rate2 = 2, consProd2 = 4, consVeille2 = 0.25, mode = 1, debug=FALSE), Serveur = "S3"));
}

library(dplyr)
df2 = df %>% group_by(Arrival_rate, Serveur) %>%
    summarize(Response = mean(Completion - Arrival), 
              Response_se = sd(Completion - Arrival)/sqrt(n()),
              Comsumption = mean(Comsumption))

library(ggplot2)

ggplot(df2, aes(x=Arrival_rate, y=Response, group = Serveur, color = Serveur)) + geom_line() + theme_bw() +
    geom_errorbar(aes(ymin = Response - 2*Response_se,
                      ymax = Response + 2*Response_se))

ggplot(df2, aes(x=Arrival_rate, y=Comsumption, group=Serveur, color=Serveur)) + geom_line() + theme_bw()