MAC122 - Princípios de Desenvolvimento de Algoritmos - IME

Quarto Exercício-Programa
Entregar até 15 de dezembro

Escalonamento de tarefas em máquinas idênticas

O objetivo deste exercício-programa é determinar um escalonamento de tarefas em máquinas idênticas.

Um problema bastante conhecido nos contextos de produção industrial e de sistemas operacionais é o de escalonamento (scheduling) de tarefas em máquinas. Estamos interessados em uma das versões mais simples do problema: dadas m máquinas idênticas e n tarefas com tempos de execução ou durações pré-determinados, encontrar uma atribuição das tarefas às máquinas que minimize o máximo dos tempos de operação das máquinas (makespan ou duração do escalonamento). Em outras palavras, encontrar um escalonamento que minimize o tempo de execução do conjunto de tarefas.

Informalmente, o problema do escalonamento em máquinas idênticas consiste no seguinte.
Dada uma sequência de inteiros positivos

   num_maquinas
   num_tarefas
   duracao[0]
   duracao[1]
   ...
   duracao[num_tarefas-1] 
onde num_maquinas é o número de máquinas, num_tarefas é o número de tarefas, e para t = 0, 1, ..., num_tarefas-1, duracao[t] é o tempo de execução da tarefa t, deseja-se distribuir todas as tarefas entre as máquinas (em outras palavras, deseja-se escalonar as tarefas) de modo que o tempo máximo de operação que qualquer uma das máquinas necessita para executar as tarefas atribuídas a ela seja o menor possível.


Exemplo:  Considere três máquinas (0, 1 e 2) e sete tarefas (0, 1, 2, 3, 4, 5 e 6) com os tempos de execução dados a seguir.

 número da tarefa  :   0   1   2   3   4   5   6
 duração           :   4   2   1   5   9   2   6

  Um possível escalonamento dessas tarefas entre as três máquinas é:

   máquina :   tarefas atribuídas     tempo total
       0   :       0    4                 13
       1   :       1    6                  8
       2   :       2    3    5             8


   duração do escalonamento : 13

   tarefas:   0 4
máquina 0:  4 9

   tarefas:   1 6
máquina 1:  2 6

   tarefas:  
2 3 5
máquina 2:  1 5 2

Um escalonamento de duração mínima para o exemplo acima é:

   máquina :   tarefas atribuídas     tempo total
       0   :       0    2    3            10
       1   :       1    5    6            10
       2   :       4                       9


  duração do escalonamento : 10

   tarefas:   0 2 3
máquina 0: 4 1 5

   tarefas:   1 5 6
máquina 1: 2 2 6

   tarefas:   4
máquina 2: 9


Algoritmos de aproximação e escalonamento de Graham

O problema do escalonamento de tarefas é um problema computacionalmente difícil. Só se consegue determinar um escalonamento de duração mínima para instâncias de tamanhos bem modestos. Por exemplo, para encontrar um escalonamento de duração mínima de 40 tarefas entre 2 máquinas pode-se consumir horas de computação. Nessa situação, é razoável tentar encontrar rapidamente um escalonamento de duração "não muito grande" em vez de um de duração mínima. Este compromisso entre perda de optimalidade e ganho de eficiência é o objetivo de algoritmos de aproximação.

O primeiro algoritmo de aproximação para o problema do escalonamento foi apresentado e analisado por R.L. Graham e usa um critério muito simples: atribuir as tarefas, uma a uma, destinando cada tarefa à máquina menos ocupada. Por esse critério, a escolha da máquina que vai receber determinada tarefa não depende dos tempos das tarefas que ainda não foram atribuídas a nenhuma máquina. É possível provar que o algoritmo de Graham encontra um escalonamento cuja duração é no máximo o dobro da duração mínima de um escalonamento.

Algoritmo de Graham

  1. para i de 0 até num_tarefas-1 faça
  2. devolva o escalonamento obtido;

Aplicando o algoritmo de Graham à instância descrita acima, obtemos o escalonamento a seguir:
   máquina :   tarefas atribuídas     tempo total
       0   :       0    5    6            12
       1   :       1    4                 11
       2   :       2    3                  6


   duração do escalonamento : 12

   tarefas:   0 5 6
máquina 0: 4 2 6

   tarefas:   1 4
máquina 1: 2 9

   tarefas:   2 3
máquina 2: 1 5

A razão de aproximação nesse caso é 12/10 = 1.2, que é de fato menor que 2, como prometido pelo algoritmo.

Existe uma variante do algoritmo de Graham que coloca as tarefas em ordem decrescente de tempo antes de começar o processo de escalonamento. Pode-se provar que essa variante encontra um escalonamento cuja duração é no máximo 4/3 da duração mínima de um escalonamento.

Variante do algoritmo de Graham

  1. para i de 0 até num_tarefas-1 faça
  2. devolva o escalonamento obtido;

Aplicando a variante do algoritmo de Graham à instância acima, podemos obter o escalonamento a seguir:
   maquina :   tarefas atribuídas     tempo total
       0   :       4    2                 10
       1   :       6    1   5             10
       2   :       3    0                  9


   duração do escalonamento : 10

   tarefas:   4 2
máquina 0: 9 1

   tarefas:   6 1 5
máquina 1: 6 2 2

   tarefas:   3 0
máquina 2: 5 4

A razão de aproximação nesse caso é 10/10 = 1.0, que é de fato menor que 4/3, como prometido pelo algoritmo. Na realidade, como vocês podem perceber, o escalonamento obtido pela variante do algoritmo de Graham é, por acaso, um escalonamento de duração mínima.

Especificações do programa

Escreva um programa que resolva o problema do escalonamento de tarefas em máquinas idênticas. O programa deve seguir o seguinte roteiro:

  1. Ler de um arquivo texto as informações referentes a uma instância do problema. As duas primeiras linhas desse arquivo contêm o número de máquinas e o número de tarefas. As linhas subsequentes contêm os tempos de execução das tarefas.

  2. Determinar um escalonamento (e sua duração) para a instância dada, utilizando o algoritmo de Graham. Sua implementação desse algoritmo deve utilizar um min-heap para obter o índice de uma máquina menos ocupada e, a cada atualização do tempo de uma máquina, deve chamar uma função "peneira" que restaure a estrutura de min-heap.

  3. Escrever no arquivo de saída todas as informações referentes ao escalonamento obtido pelo algoritmo de Graham.

  4. Determinar um escalonamento de duração mínima, usando um algoritmo iterativo, que faça busca exaustiva (backtracking). Para diminuir o número de escalonamentos construídos pelo seu algoritmo, utilize como limitante a duração do melhor escalonamento dentre os já encontrados. Escalonamentos de duração maior que o valor atual do limitante não devem ser construídos completamente. Como valor inicial do limitante, use a duração do escalonamento obtido pelo algoritmo de Graham. Ao final da construção de cada escalonamento, devem ser apresentados na saída padrão o número e a duração do escalonamento. (O número do escalonamento é o valor de um contador sequencial.)

    Seu algoritmo de busca exaustiva pode supor que a tarefa 0 é sempre atribuída à máquina 0. (Note que não há perda de generalidade com essa suposição, pois as máquinas são idênticas.)

  5. Escrever no arquivo de saída todas as informações referentes ao escalonamento de duração mímima obtido pela busca exaustiva.

Os nomes dos arquivos de entrada e de saída devem ser fornecidos na linha de comando. O primeiro argumento é o nome do arquivo de entrada e o segundo é o nome do arquivo de saída.

O arquivo de saída deve conter as seguintes informações:

Um exemplo de saída

A seguir escrevemos uma possível saída (parcial) para a instância descrita acima.

(1) Informações gerais:

    número de máquinas = 3     número de tarefas = 7

    número da tarefa  :   0   1   2   3   4   5   6
    duração           :   4   2   1   5   9   2   6

(2) Informações sobre o escalonamento obtido pelo algoritmo de Graham:

   - Duração do escalonamento : 12

   - Tempo total de trabalho de cada máquina

         máquina     tempo total
            0            12
            1            11
            2             6

   - Tarefas atribuídas a cada máquina

         máquina     tarefas atribuídas
            0            0    5    6  
            1            1    4  
            2            2    3

   - Número de tarefas atribuídas a cada máquina

         máquina     número de tarefas
            0              3
            1              2
            2              2

   - Informações de cada tarefa

         tarefa    duração    máquina
           0          4          0
           1          2          1
           2          1          2
           3          5          2
           4          9          1
           5          2          0
           6          6          0


(3) Informações sobre o escalonamento de duração mínima obtido pela busca exaustiva:

      ... [informações similares às do ítem (2) acima]

(4) Razão de aproximação do escalonamento obtido pelo algoritmo
      de Graham : 12/10 = 1.2

Requisitos sobre a implementação

Variáveis relevantes da função principal. Não utilize variáveis globais. A função principal deve conter as variáveis que aparecem no seguinte fragmento de código:
int main(int argc, char *argv[])
{
    FILE *entrada;      /* arquivo de entrada */
  
    FILE *saida;        /* arquivo de saida */

    int no_maquinas;    /* numero de maquinas */

    int no_tarefas;     /* numero de tarefas */

    int *duracao;       /* vetor de duracoes das tarefas:
                           duracao[t] e' a duracao da tarefa t */

    int *t_maquina;     /* vetor com os tempos de trabalho das maquinas:
	 	           t_maquina[m] contem a soma das duracoes das
                           tarefas associadas `a maquina m */

    int *escalonamento; /* vetor com um escalonamento:
                           escalonamento[t] contem o indice da maquina
                           associada `a tarefa t */
   
    int duracao_graham; /* duracao de um escalonamento de Graham */

    int duracao_min;    /* duracao de um escalonamento de duracao minima */

    ...
}
Outras funções. Implemente em seu programa, obrigatoriamente, as seguintes funções:
  1. int escalonamento_graham(int no_tarefas, int duracao[], int no_maquinas,
                             int t_maquina[], int escalonamento[]);
    Esta função recebe nos três primeiros parâmetros uma instância do problema do escalonamento. Ela armazena em t_maquina e escalonamento as informações sobre um escalonamento obtido com o algoritmo de Graham e devolve como valor da função a duração desse escalonamento.

    É dentro da função escalonamento_graham que deve estar o min-heap mencionado no ítem 2 das especificações do programa. (Dica: Os elementos do min-heap podem ser os índices das máquinas. Nesse caso, se o vetor h for o min-heap, então cada h[i] será um índice de máquina. Note que a comparação entre h[i] e h[j] deve ser baseada nos tempos das máquinas correspondentes, isto é, em t_maquina[h[i]] e t_maquina[h[j]].)

  2. int escalonamento_busca_exaustiva(int limitante, 
                                      int no_tarefas, int duracao[], int no_maquinas,
                                      int t_maquina[], int escalonamento[]);
    Determina um escalonamento de duração mínima, por busca exaustiva, e devolve como valor da função a duração desse escalonamento. O parâmetro limitante é o valor inicial do limitante mencionado no ítem 4 das especificações do programa. Os demais parâmetros são análogos aos da função escalonamento_graham.

  3. void peneira_min_heap(int p, int no_maquinas, int h[], int t_maquina[]);
    Esta é a função peneira do min-heap usado pelo algoritmo de Graham.

  4. void imprime_escalonamento (FILE *saida, int duracao_escalonamento,
                                int no_tarefas, int duracao[], int no_maquinas,
                                int t_maquina[], int escalonamento[]);
    Escreve no arquivo especificado pelo parâmetro saida as informações sobre o escalonamento representado pelos demais parâmetros. Essas informações devem aparecer como no exemplo de saída fornecido.

Observações


Last modified: Fri Nov 27 18:22:41 BRST 2009