A apresentação está carregando. Por favor, espere

A apresentação está carregando. Por favor, espere

IV Escola Regional de Alto Desempenho de São Paulo

Apresentações semelhantes


Apresentação em tema: "IV Escola Regional de Alto Desempenho de São Paulo"— Transcrição da apresentação:

1 IV Escola Regional de Alto Desempenho de São Paulo
Programação Paralela usando Memórias Transacionais: da Academia à Indústria IV Escola Regional de Alto Desempenho de São Paulo julho de 2013 Guido Araujo (IC-UNICAMP) Alexandro Baldassin (IGCE-UNESP) Blue Gene/Q

2 Roteiro Parte 1 Parte 2 Programação paralela na atualidade
Transações: uma estratégia otimista Modelo de execução Parte 2 Arquitetura básica de uma STM Exemplo usando o GCC Arquitetura básica de uma HTM IBM BlueGene Q

3 Introdução e Conceitos Básicos
Parte 1 Introdução e Conceitos Básicos

4 Sistema de memória compartilhada
temp store (counter, temp) temp load (temp, counter) counter Usado como modelo de execução de Pthreads e OpenMP

5 Memória Compatilhada Processo cria várias threads de execução
Threads compartilham espaço de endereçamento Comunicação é feita diretamente através de leituras e escritas em memória compartilhada Acessos concorrentes de leitura e escrita podem causar inconsistências (condições de corrida) Sincronização é usada para evitar tais cenários

6 Sincronização Evitar intercalações inconsistentes de execução
shared counter; void work() { counter++; } shared counter; void work() { counter++; } Qual o resultado esperado após a primeira execução? 1a coisa: sentença na linguagem de programação != instruções que o processador executa

7 Sincronização Evitar intercalações inconsistentes de execução
shared counter; void work() { counter++; } i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); Condição de corrida na variável ‘counter’

8 Sincronização Evitar intercalações inconsistentes de execução
t1 (counter++) t2 (counter++) i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); Exemplo de execução com duas threads – resultado esperado para counter == 2

9 Sincronização Evitar intercalações inconsistentes de execução
t1 (counter++) t2 (counter++) i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp);

10 Sincronização Evitar intercalações inconsistentes de execução
t1 (counter++) t2 (counter++) i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); O que ocorreu aqui?

11 Sincronização Evitar intercalações inconsistentes de execução
t1 (counter++) t2 (counter++) i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); Qual o valor lido?

12 Sincronização Evitar intercalações inconsistentes de execução
t1 (counter++) t2 (counter++) i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp);

13 Sincronização Evitar intercalações inconsistentes de execução
t1 (counter++) t2 (counter++) i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp);

14 Sincronização Evitar intercalações inconsistentes de execução
t1 (counter++) t2 (counter++) i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp);

15 Sincronização Evitar intercalações inconsistentes de execução
t1 (counter++) t2 (counter++) i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); i1: temp = load(counter); i2: temp = temp + 1; i3: store(counter, temp); Resultado final deveria ser 2, mas acabou sendo 1. Está correto?

16 Mecanismos de Sincronização
Bloqueantes travas (locks) variáveis de condição (condition variables) semáforos/monitores Não-bloqueantes livre de espera (wait-free) livre de trava (lock-free) livre de obstrução (obstruction-free) Nao entrar em detalhes sobre garantias de progresso nas não-bloqueantes. Terminar dizendo que os mecanismos serão vistos com mais detalhes no que se segue.

17 Exemplo Considere o seguinte problema: implementar uma lista de inteiros ordenados em ordem crescente, admitindo operações como inserção, remoção e consulta Esta tarefa pode ser desenvolvida facilmente por alunos de disciplinas de introdução à computação Desejamos uma versão paralela, que permita operações concorrentes na lista.

18 Lista ordenada – sequencial
class Node { int key; Node next; } class List { private Node head; public List() { head = new Node(Integer.MIN_VALUE); head.next = new Node(Integer.MAX_VALUE); } Implementação da lista de inteiros ordenada como uma lista ligada. Nós sentinelas… facilitar a programação

19 Lista ordenada – sequencial
head tail public boolean add(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; return true; else return false; Função retorna se conseguiu inserir item ou não (item repetido ou não) Lista contém os items ‘a’ e ‘c’ - Inserir nodo ‘b’ Importante entender essa versão para ver como paralelizar depois

20 Lista ordenada – sequencial
pred curr a c head tail add(b) public boolean add(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; return true; else return false; Fico varrendo a lista até achar um elemento que seja maior ou igual ao curr.key -> nesse momento, pred aponta para item anterior

21 Lista ordenada – sequencial
pred curr a c head tail add(b) public boolean add(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; return true; else return false; Por construção não pode haver nenhum item na lista maior do que o que está sendo inserido -> evita necessidade de checar por NULL

22 Lista ordenada – sequencial
pred curr a c head tail b add(b) public boolean add(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; return true; else return false;

23 Lista ordenada – sequencial
pred curr a c head tail b add(b) public boolean add(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; return true; else return false;

24 Lista ordenada – sequencial
pred curr a c head tail b add(b) public boolean add(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; return true; else return false;

25 Lista ordenada – sequencial
pred curr pred curr a c a b c head tail b head tail public boolean add(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; return true; else return false; public boolean remove(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item == curr.key) { pred.next = curr.next; return true; else return false; remove(b) Remover ‘b’ Mesma ideia que ‘add’, porém quando acho o item a ser removido, faço pred.next = curr.next

26 Paralelizando Como desenvolver uma versão paralela do exemplo anterior? Sendo otimista Operações de inserção, remoção e busca podem “potencialmente” ser executadas em paralelo Thread 2 search(b) Thread 1 remove(d) a b c d f head tail

27 Paralelizando Como desenvolver uma versão paralela do exemplo anterior? Sendo otimista Operações de inserção, remoção e busca podem “potencialmente” ser executadas em paralelo Mais seguro Ser pessimista e assumir que sempre haverá conflitos Lock global

28 Lista ordenada – lock global
public boolean add(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; return true; else return false;

29 Lista ordenada – lock global
public boolean add(int item) { Node pred, curr; boolean valid = false; lock.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; valid = true; lock.unlock(); return valid; public boolean add(int item) { Node pred, curr; pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; return true; else return false; Solução simples e correta!!! Seção crítica em negrito

30 Lista ordenada – lock global
Ideia do lock global Antes de iniciar o trecho de código que altera a lista, adquirir a trava (lock) Após trecho de código, liberar a trava (unlock) Funciona? Solução simples, mas não escala! Operações são serializadas Enfatizar o fato que com o lock global as operações serão serializadas (tradeoff entre corretude e desempenho) -> lei de Amdahl ((Uma forma possível de melhorar a solução é usar outra estrutura de dados (e.g. árvore vermelho-preto))) Focar em otimizar o caso que usa lista ligada como estrutura de dados.

31 Serializando Como melhorar a solução? Sugestões?
public boolean add(int item) { Node pred, curr; boolean valid = false; lock.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; valid = true; lock.unlock(); return valid; Como melhorar a solução? Sugestões? public boolean add(int item) { Node pred, curr; boolean valid = false; lock.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; valid = true; lock.unlock(); return valid;

32 Lista ordenada – locks finos
Ideia Associar um lock a cada nó da lista Antes do conteúdo do nó ser acessado, adquirimos seu respectivo lock (liberando-o após o acesso) Essa abordagem funciona? Considere duas operações concorrentes para remoção dos itens ‘b’ e ‘a’, por duas threads distintas (T1 e T2) Se o problema é devido ao fato das operações estarem sendo serializadas, que tal usar um lock para cada nodo? A ideia é que isso possa permitir operações concorrentes. Note que a solução pode não ser muito boa se o custo de aquisição/liberação dos locks for alta. Esquema simples: antes de acessar o nó o travamos – depois de visitar, liberamos o respectivo nó. A ideia é evitar conflito read/write em uma determinada posição, e portanto precisamos travá-la antes de acessar.

33 Lista ordenada – locks finos
b c head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; T1

34 Lista ordenada – locks finos
curr pred a b c head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; T1

35 Lista ordenada – locks finos
curr pred a b c head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; T1

36 Lista ordenada – locks finos
curr pred a b c head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; T1

37 Lista ordenada – locks finos
curr pred a b c head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; Assuma um “page fault” aqui!! T1

38 Lista ordenada – locks finos
curr pred a b c head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; remove(a) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; T1 T2

39 Lista ordenada – locks finos
curr pred a b c pred curr head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; remove(a) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; T1 T2

40 Lista ordenada – locks finos
curr pred a b c pred curr head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; remove(a) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; T1 T2 Thread azul volta

41 Lista ordenada – locks finos
curr pred a b c pred curr head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; remove(a) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; T1 T2 Resultado?

42 Lista ordenada – locks finos
“a” ainda ficou!! a b c head tail remove(b) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; remove(a) ... head.lock(); pred = head; curr = pred.next; while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; pred.lock(); } if (item == curr.key) { pred.next = curr.next; valid = true; return valid; Só um item foi removido (eram para ser 2) T1 T2

43 Lista ordenada – locks finos
Antes de alterar um nó, uma thread necessita adquirir as travas para o nó atual e o próximo Note que as threads envolvidas precisam adquirir os locks na mesma ordem para evitar o risco de deadlock Não é trivial provar a corretude! Exemplo de código para a operação de inserção ...

44 Lista ordenada – locks finos
public boolean add(int item) { boolean valid = false; head.lock(); Node pred = head; Node curr = pred.next; curr.lock(); while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; } if (item != curr.key) { Node newNode = new Node(item); newNode.next = curr; pred.next = newNode; valid = true; curr.unlock(); return valid; Lock coupling é o nome da técnica.

45 Lista ordenada – locks finos
public boolean add(int item) { boolean valid = false; head.lock(); Node pred = head; Node curr = pred.next; curr.lock(); while (curr.key < item) { pred.unlock(); pred = curr; curr = curr.next; } if (item != curr.key) { Node newNode = new Node(item); newNode.next = curr; pred.next = newNode; valid = true; curr.unlock(); return valid; Lock coupling é o nome da técnica. Grande parte do código é específico para sincronização (6 de 18 linhas = ~33%)

46 Problemas com lock finos
Risco alto de deadlock Diferentes locks adquiridos em diferentes ordens Operações lock e unlock custosas Geralmente envolvem alguma forma de syscall Dificuldade relacionada a engenharia de software Como encapsular um método com locks? Como compor código?

47 Composição de código com locks
Imagine que nossa aplicação precise utilizar as operações da lista ligada para implementar uma outra operação de nível mais alto, como mover um elemento de uma lista para outra Não temos acesso ao código fonte Apenas sabemos que cada operação é atômica public boolean move(List new, List old, int item) { old.remove(item); new.add(item); }

48 Composição de código com locks
Imagine que nossa aplicação precise utilizar as operações da lista ligada para implementar uma outra operação de nível mais alto, como mover um elemento de uma lista para outra Não temos acesso ao código fonte Apenas sabemos que cada operação é atômica Outras threads podem ‘enxergar’ estado intermediário entre as operações de remoção e adição (estado inconsistente) public boolean move(List new, List old, int item) { old.remove(item); new.add(item); } Atômico?

49 Composição de código com locks
Colocar um lock global? public boolean move(List from, List to, int item) { newlock.lock(); from.remove(item); to.add(item); newlock.unlock(); } E se ocorrer busca(item,from) em outra thread neste ponto? Funciona? Note que a introdução de um novo lock global não garante atomicidade entre a operação ‘move’ e as demais da lista – todas as operações precisam ser envolvidas (wrapped) pelo menos lock

50 Composição de código com locks
Colocar um lock global? Esta solução requer que todas as operações atômicas da lista sejam envoltas pelo novo lock Uma solução alternativa seria quebrar o encapsulamento e expor a implementação da lista (quais locks foram usados) public boolean move(List from, List to, int item) { newlock.lock(); from.remove(item); to.add(item); newlock.unlock(); } Locks finos -> qual o protocolo usado? Para continuar, supor que apenas um lock global é exposto por lista.

51 Composição de código com locks
Cada lista expõe seu lock global Esta solução funciona? public boolean move(List from, List to, int item) { from.lock(); to.lock(); from.remove(item); to.add(item); from.unlock(); to.unlock(); }

52 Composição de código com locks
Cada lista expõe seu lock global Esta solução funciona? public boolean move(List from, List to, int item) { from.lock(); to.lock(); from.remove(item); to.add(item); from.unlock(); to.unlock(); } Thread 1 Thread 2 move(lista_clientes, lista_devedores, USER1); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock(); move(lista_devedores, lista_clientes, USER2); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock();

53 Composição de código com locks
Cada lista expõe seu lock global Esta solução funciona? public boolean move(List from, List to, int item) { from.lock(); to.lock(); from.remove(item); to.add(item); from.unlock(); to.unlock(); } Thread 1 Thread 2 move(lista_clientes, lista_devedores, USER1); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock(); move(lista_devedores, lista_clientes, USER2); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock();

54 Composição de código com locks
Cada lista expõe seu lock global Esta solução funciona? public boolean move(List from, List to, int item) { from.lock(); to.lock(); from.remove(item); to.add(item); from.unlock(); to.unlock(); } Thread 1 Thread 2 move(lista_clientes, lista_devedores, USER1); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock(); move(lista_devedores, lista_clientes, USER2); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock();

55 Composição de código com locks
Cada lista expõe seu lock global Esta solução funciona? public boolean move(List from, List to, int item) { from.lock(); to.lock(); from.remove(item); to.add(item); from.unlock(); to.unlock(); } Thread 1 Thread 2 move(lista_clientes, lista_devedores, USER1); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock(); move(lista_devedores, lista_clientes, USER2); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock();

56 Composição de código com locks
Cada lista expõe seu lock global Esta solução funciona? public boolean move(List from, List to, int item) { from.lock(); to.lock(); from.remove(item); to.add(item); from.unlock(); to.unlock(); } Solução: ordenar a ordem de acquisição dos locks Thread 1 Thread 2 move(lista_clientes, lista_devedores, USER1); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock(); move(lista_devedores, lista_clientes, USER2); from.lock(); to.lock(); from.remove(USER1); to.add(USER1); from.unlock(); to.unlock(); DEADLOCK

57 Programação concorrente
Desempenho Lock global Complexidade

58 Programação concorrente
Lock-free Locks finos Desempenho Lock global Complexidade

59 Programação concorrente
HTM Lock-free Locks finos TM Desempenho STM TM – ser tão simples de programar quanto lock global, com o desempenho de locks finos. Lock global Complexidade

60 Memória Transacional (TM)
X X2 i1: temp2 = temp1; i2: temp2 = temp2 + 1; i3: store(counter, temp2); i1: temp1 = load(counter); i2: temp1 = temp1 + 1; i3: store(counter, temp1); RAW Conceito de transação vem de BD – durabilidade não se aplica -> muita coisa desenvolvida pelo pessoal de BD (IBM) Fazer algum comentário sobre nome? Um tanto quanto ‘misleading’ Foco de TM: aplicações irregulares com pouca contenção 1 antes de 2 SUCESSO!!

61 Memória Transacional (TM)
X X2 i1: temp2 = temp1; i2: temp2 = temp2 + 1; i3: store(counter, temp2); i1: temp1 = load(counter); i2: temp1 = temp1 + 1; i3: store(counter, temp1); RAW Conceito de transação vem de BD – durabilidade não se aplica -> muita coisa desenvolvida pelo pessoal de BD (IBM) Fazer algum comentário sobre nome? Um tanto quanto ‘misleading’ Foco de TM: aplicações irregulares com pouca contenção 2 antes de 1 ABORTA!!

62 Memória Transacional (TM)
No modelo transacional, programadores usam o conceito de transação como abstração Atomicidade Consistência Isolamento Vantagens Nível de abstração maior Potencial ganho de desempenho Dependente de implementação (visto mais adiante) Composição de código Conceito de transação vem de BD – durabilidade não se aplica -> muita coisa desenvolvida pelo pessoal de BD (IBM) Fazer algum comentário sobre nome? Um tanto quanto ‘misleading’ Foco de TM: aplicações irregulares com pouca contenção

63 Memória Transacional (TM)
No modelo transacional, programadores usam o conceito de transação como abstração Atomicidade Consistência Isolamento Vantagens Nível de abstração maior Potencial ganho de desempenho Dependente de implementação (visto mais adiante) Composição de código Detalhes de como realizar a sincronização são movidos do programador para o sistema de execução Pelo fato da sincronização ficar a cargo do sistema de execução, uma abordagem otimista pode ser adotada (não estática, mas dinâmica) – drawback -> desempenho Atomicidade aqui é no sentido de ‘failure atomicity” Consistência é dependente de aplicação

64 Programando com TM public boolean add(int item) { Node pred, curr; boolean valid = false; atomic { pred = head; curr = pred.next; while (curr.key < item) { pred = curr; curr = curr.next; } if (item != curr.key) { Node node = new Node(item); node.next = curr; pred.next = node; valid = true; if (valid) return true; return false; Programador delimita a região que deve ser executada atomicamente Exemplo com lista ligada Sistema de execução (pode ser hardware ou software) cuida de garantir atomicidade, isolamento e consistência Diferentemente de locks com granularidade grossa, não é necessário associar variáveis a regiões críticas

65 TM e composição de código
O modelo transacional permite composição de código de forma natural Aninhamento de transações atomic { } A forma que transações aninhadas são implementadas podem variar: open, close, flat nesting

66 TM e composição de código
O modelo transacional permite composição de código de forma natural Aninhamento de transações Exemplo anterior para mover elementos entre listas atomic { } public boolean move(List new, List old, int item) { atomic { old.remove(item); new.add(item); }

67 Bom para quê? Estruturas de dados cuja escalabilidade é ruim com abordagens baseadas em locks Exemplo: árvore rubro-negra Aplicações nas quais o uso de lock é muito conservativo Aplicações irregulares (uso extensivo de ponteiros) Algoritmos de grafos Exemplo de sistema grande de porte Servidor do jogo Quake (Zyulkyarov et al.)

68 Implementação O mecanismo transacional precisa fornecer…
Versionamento de dados Os dados temporários (especulativos) usados pela transação precisam ser mantidos em algum local Essencial para garantiar atomicidade e consistência Isolamento da execução É necessário um mecanismo para detectar e resolver os conflitos entre transações

69 Versionamento de dados
Imediato (eager/pessimistic/direct) Memória compartilhada atualizada imediatamente (valor antigo armazenado em buffer) Efetivação rápida, mas cancelamento lento Deferido (lazy/optimistic/deferred) Armazena atualização em buffer interno Cancelamento rápido, mas efetivação lenta

70 Versionamento imediato

71 Versionamento imediato

72 Versionamento imediato

73 Versionamento imediato

74 Versionamento deferido

75 Versionamento deferido

76 Versionamento deferido

77 Versionamento deferido

78 Isolamento da execução
Detecção de conflitos Usa-se dois conjuntos: Read set: dados lidos Write set: dados escritos Conflito se há intersecção entre o conjunto de leitura e escrita de transações diferentes Resolução de conflitos Depende do gerenciador de contenção Exemplo: abortar imediatamente, esperar, ... Importante para garantir progresso Gerenciador de contenção importante para garantir progresso.

79 Formas de detecção de conflitos
Adiantado (eager/pessimistic/encounter-time) Ocorre no momento dos acessos ao dados Pode evitar executar código desnecessário Mais suscetível a livelock Tardio (lazy/optimistic/commit-time) Ocorre na efetivação da transação Potencialmente menos conflitos Menos suscetível a livelock, porém starvation pode ser um problema

80 Detecção de conflitos adiantado

81 Detecção de conflitos adiantado
Assumindo que o gerenciador de contenção reiniciou A.

82 Detecção de conflitos adiantado
Writer wins

83 Detecção de conflitos adiantado
Política: writer aborta outra transação Writer adiantado, reader tardio Livelock

84 Detecção de conflitos tardio
Necessário que versionamento seja tardio!

85 Detecção de conflitos tardio

86 Detecção de conflitos tardio

87 Detecção de conflitos tardio
Livelock também pode acontecer aqui (durante o commit, na aquisição dos locks, duas transações podem abortar)

88 Implementação em STM e HTM
Parte 2 Implementação em STM e HTM

89 Implementação de TM O suporte transacional pode ser realizado em hardware, software ou uma mescla de ambos (híbrido) Hardware (HTM) Melhor desempenho Problemas com virtualização (espaço, tempo) Software (STM) Desempenho depende muito da aplicação Extremamente flexível Ideal para testar novas ideias Espaço -> cache overflow; tempo -> sobreviver context-switches? Comunidade pessimista em relação a STM, mas a grande vantagem é a questão de testar novas ideias

90 STM – Suporte Interface Duas formas principais de implementação
API básica start, commit, barreiras de leitura e escrita Object vs word Duas formas principais de implementação Non-blocking Blocking As implementações possuem diferentes garantias de progresso e consistência

91 STM – Shavit & Touitou (1995)
PODC’95 Lock-free synchronization as efficient? Talvez naquela época ainda isso acontecesse?

92 STM – Shavit & Touitou (1995)
Cunhou o termo “software transactional memory” (STM) Transações eram “estáticas” Uma transação precisa especificar, de forma antecipada, um vetor com as posições de memória que serão acessadas Basicamente um CAS múltiplo A implementação, non-blocking, influenciou as novas propostas que surgiriam em seguida Transação também precisa expressar as atualizações na memória como uma função que mapeia os valores vistos na memória para novos valores. Implementação complexa – esquema de ajuda típico de algoritmos lock-free

93 DSTM – Herlihy et al. (2003) PODC’03

94 DSTM – Herlihy et al. (2003) Definitivamente desencadeou a pesquisa em STM (junto com Harris & Fraser – OOPSLA 2003) Granularidade: objetos Detecção de conflitos adiantada/versionamento deferido Implementação é obstruction-free Gerenciador de contenção é responsável por garantir progresso Proposto para Java Gerenciador de contenção deve evitar livelock e starvation

95 Rob Ennals (2005, 2006) Motivado pelo baixo desempenho de implementações non-blocking, Rob Ennals propôs implementar sistemas STM baseados em locks Efficient Software Transactional Memory (2005) “This paper described how software transactional memory could be made more efficient if one was prepared to sacrifice non-blocking properties. It was rejected from SPAA, but was widely circulated and was fairly influential on the design of subsequent STM implementations.” Software Transactional Memory Should Not Be Obstruction-Free (2006) “This paper was submitted to SCOOL 2005, but was deemed to be "too controversial for publication" and so was instead made the topic of a panel instead.” Rob Ennals até então Intel, agora (2012) Google Textos tirados da antiga página do autor na web ( Interessante notar que mesmo com trabalhos sendo rejeitados, a ideia ‘vingou’ no final

96 Lock-based STMs A ideia de Ennals gerou bastante controvérsia
Até então acreditava-se que as implementações deveriam ser non-blocking por questões de garantia de progresso Ennals argumentou que o sistema runtime de linguagens modernas podem controlar o escolamento das threads, evitando o problema de preempção As principais implementações (mais rápidas) de STM atuais são blocking Deadlock pode ser evitado na implementação

97 TL2 – Dice et al. (2006) DISC’06 Outros exemplos: Intel McRT, Microsoft Bartok

98 TL2 – Dice et al. (2006) Principal representante de uma classe de implementações que adotam locks Granularidade: principalmente palavras Locks para escrita adquiridos durante fase de commit Versionamento deferido Relógio global é usado para manter consistência Consistência: opacity Grande novidade: relógio global

99 TL2 – Interface API Exemplo: StartTx(), CommitTx(), AbortTx()
ReadTx(), WriteTx() Exemplo: void PushLeft(DQueue *q, int val) { QNode *qn = malloc(sizeof(QNode)); qn->val = val; do { StartTx(); QNode *leftSentinel = ReadTx(&(q->left)); ... WriteTx(&(oldLeftNode->left), qn); } while (!CommitTx()); } Observar que essa API é comum à maioria das STMs

100 TL2 – Interface Lembrando que potencialmente o compilador gera esse código a partir de blocos atômicos API StartTx(), CommitTx(), AbortTx() ReadTx(), WriteTx() Exemplo: void PushLeft(DQueue *q, int val) { QNode *qn = malloc(sizeof(QNode)); qn->val = val; do { StartTx(); QNode *leftSentinel = ReadTx(&(q->left)); ... WriteTx(&(oldLeftNode->left), qn); } while (!CommitTx()); }

101 TL2 – Metadados . Compartilhado Global Clock
Ownership Record Table (ORT) GCLOCK . versioned locks

102 TL2 – Metadados . Compartilhado
Global Clock Ownership Record Table (ORT) GCLOCK . versioned locks Sempre incrementado (+2) quando uma transação é efetivada

103 TL2 – Metadados . Compartilhado
Global Clock Ownership Record Table (ORT) GCLOCK . versioned locks Toda posição de memória acessada transacionalmente é mapeada para um registro (versioned lock) nessa tabela através de uma função hash

104 TL2 – Metadados . Compartilhado Global Clock
Ownership Record Table (ORT) GCLOCK . versioned locks Agora é possível entender o motivo do GCLOCK ser incrementado em 2 versão da palavra (se lock bit ==0) ou endereço da transação que travou registro lock bit

105 TL2 – Metadados Privado descritor transação (txdsc) Ativa, Abortada,
status versão conjunto de leitura (cjtLeitura) conjunto de escrita (cjtEscrita) endereço valor Ativa, Abortada, Efetivada Agora é possível entender o motivo do GCLOCK ser incrementado em 2

106 TL2 – Funcionamento GCLOCK StartTx() 1. txdsc.versao <- GCLOCK
2. txdsc.status <- ACTIVE descritor transação (txdsc) status versão conjunto de leitura (cjtLeitura) conjunto de escrita (cjtEscrita) endereço valor

107 TL2 – Funcionamento GCLOCK descritor transação (txdsc)
status versão conjunto de leitura (cjtLeitura) conjunto de escrita (cjtEscrita) endereço valor WriteTx(endereco, valor) 1. cjtEscrita.insere(endereco, valor)

108 TL2 – Funcionamento . ORT GCLOCK descritor transação (txdsc)
ORT-address = hash(endereço) descritor transação (txdsc) status versão conjunto de leitura (cjtLeitura) conjunto de escrita (cjtEscrita) endereço valor ReadTx(endereco, valor) 1. se (endereco em cjtEscrita) retorna cjtEscrita[endereco].valor 2. v1 = ORT[hash(endereco)] 3. valor = memoria[endereco] 4. v2 = ORT[hash(endereco)] 5. se (v1.lock travado || v1 != v2 || v1.versao > txdsc.versao) aborta transacao cjtLeitura.insere(endereco) retorna valor Notem o overhead. v1 = versioned locked de antes v2 – versioned locked de depois

109 TL2 – Funcionamento . ORT GCLOCK descritor transação (txdsc)
ORT-address = hash(endereço) CommitTx(endereco, valor) 1. trava elementos em CjtEscrita 2. incrementa GCLOCK (+2) 3. valida CjtLeitura 4. atualiza memória com valores no CjtEscrita 5. destrava CjtEscrita e atualiza versao na ORT descritor transação (txdsc) status versão conjunto de leitura (cjtLeitura) conjunto de escrita (cjtEscrita) endereço valor Notem o overhead. Memória Compartilhada

110 TL2 – Características Contenção em GCLOCK pode ser tornar crítico para um número muito grande de transações concorrentes Implementação cuidadosa Deve evitar deadlock Commit-time locking (CTL) com versionamento tardio Conflitos write-after-write (WAW) e read-after-write (RAW) detectados de forma tardia Write-after-read não tem como – invisible readers

111 Novas lock-based STMs A maioria das STMs que surgiram depois usam o mesmo conceito de relógio global para garantir consistências (time-based STMs) Principais variações Encounter-time locking (ETL) com versionamento tardio ou adiantado, com extensão de versões Tipos de conflitos (read-after-write, write-after-write) tratados de formas diferentes Exemplos de STMs da mesma classe TinySTM, SwisSTM

112 Críticas sobre STMs Comm. ACM’08 Queue’08

113 Research toy

114 STM strikes back Comm. ACM’11

115 STM strikes back Comm. ACM’11 Autores argumentam que artigo anterior usou conjunto de aplicações/hardware inadequado

116 STM strikes back

117 Suporte transacional no GCC 4.7
Suporte experimental a TM existe no GCC a partir da versão 4.7 (abril de 2012) As construções adicionadas à linguagem são baseadas no documento “Draft Specification of Transactional Language Constructs for C++”, versão 1.1 “The support is experimental. In particular, this also means that several parts of the implementation are not yet optimized. If you observe performance that is lower than expected, you should not assume that transactional memory is inherently slow; instead, please just file a bug.”

118 Construções GCC Principais construções Anotações
__transaction_atomic {…} __transaction_relaxed {…} __transaction_cancel Anotações __attribute__((transaction_safe)) __attribute__((transaction_pure)) Opção -fgnu-tm deve ser passada ao compilador transaction_relaxed: permite operações que não podem ser desfeitas (I/O) minha experiência com o gcc é que não é suportado totalmente (código gerado é o mesmo que com _atomic, a não ser pela verificação feita pelo compilador) Por default, qualquer função é ‘transaction_safe’ para todos os calls dentro de um módulo.

119 Exemplo com GCC – lista ligada
int list_add(list_node_t *head, int item) { list_node_t *pred, *curr; __transaction_atomic { pred = head; curr = head->next; while (curr->key < item) { pred = curr; curr = curr->next; } list_node_t *node = (list_node_t *)malloc(sizeof(list_node_t)); node->key = item; node->next = curr; pred->next = node; return 1; Demais operações implementadas da mesma forma

120 Teste de “stress” da lista
void *list_exercise(void *arg) { int operations = (int)arg; int add_or_remove, chance, value, last_value = 0; add_or_remove = 1; /* 1 - add, 0 - remove */ while (operations--) chance = (int)(erand48(seed)*100); value = (int)(erand48(seed)*RANGE*2); if (chance <= UPD_RATE) { if (add_or_remove) { list_add(linked_list, value); last_value = value; } else list_remove(linked_list, last_value); add_or_remove ^= 1; else list_contain(linked_list, value); Todas as threads executam a mesma rotina

121 Resultados “informais”
Máquina: AMD Opteron Taxa de atualização (UPD_RATE): 20% Tamanho do conjunto: 100 elementos

122 Compondo código usando TM
Movendo elemento de uma lista para outra Se list_remove e list_add forem definidos em outro arquivo, é necessário usar transaction_safe void move(list_node_t *from, list_node_t *to, int val) { __transaction_atomic { if (!list_remove(from, val)) __transaction_cancel; list_add(to, val); } __attribute__((transaction_safe)) int list_add(list_node_t *head, int item);

123 Detalhes da geração e execução
O compilador gera duas versões para cada rotina especificada com o atributo transaction_safe A versão transacional é usada quando a rotina é chamada dentro de uma transação O código gerado é linkado com uma biblioteca de runtime chamada libitm Essa biblioteca pode ser substituída em tempo de execução Permite que diferentes implementações possam ser avaliadas de forma simples Especificação segue basicamente a ABI proposta pela Intel Intel Transactional Memory Compiler and Runtime Application Binary Interface, revisão 1.1 – maio de 2009 Mais detalhes sobre a implementação em software serão vistos mais a frente

124 HTM – Suporte Interface Versionamento Conflitos
Conjunto de instruções do processador Exemplo: Intel TSX Versionamento Cache ou buffer de escrita Conflitos Protocolo de coerência de cache (snoop ou diretório) R/W bits adicionados à cache Explícito -> instruções para carregar ou armazenar valores transacionais | implícitos -> basicamente, iniciar/terminar transações Como os requisitos são implementados em hardware … Caching in a multiprocessor system results in multiple copies of a memory location being present in different caches. Hardware cache coherence provides mechanisms to locate all cached copies of a memory location and to keep all such cached copies up-to-date. The MESI states are the most common states in commercialmultiprocessors. Checkpointing support

125 Versionamento e detecção de conflitos atrasados
HTM – Exemplo Execução Explicar a figura: caches aumentadas com bits de R/W Em geral, HTMs usam detecção de conflitos adiantado – versionamento é sempre atrasado (cache/write buffer) Versionamento e detecção de conflitos atrasados

126 HTM – Exemplo Execução

127 HTM – Exemplo Execução

128 HTM – Exemplo Execução

129 HTM – Exemplo Execução No commit, as informações de escrita são colocadas no barramento – outros processadores escutam essa informação e tomam uma decisão adequada (e.g., abortar)

130 HTM – Herlihy & Moss (1993) ISCA’93
Lock-free synchronization as efficient? Talvez naquela época ainda isso acontecesse?

131 HTM – Herlihy & Moss (1993) Novas instruções (6)
ST, LT, LTX COMMIT, VALIDATE, ABORT Cache transacional separada Mantém os conjuntos de leitura e escrita da transação Protocolo de coerência Baseado em snoop Transação automaticamente é iniciada quando a primeira instrução transacional é executada. LTX – hint que o valor carregado vai ser escrito em breve Quando status da transação muda para abortada, transação continua executando – VALIDATE deve ser usado para software detectar consistência

132 Problemas com a abordagem
Cache adicional (em paralelo com as de dado e instruções) complica bastante o projeto de processadores modernos Estender cache de dados tornou-se mais popular (1 bit extra para read, e outro para escrita especulativa) Nenhuma virtualização E se a cache transbordar? Uso ainda requer programador experiente Complicação cache adicional -> “olhar” para várias caches simultaneamente para descobrir onde está o dado

133 Novos HTMs Sun’s Rock 2009 – AMD Advanced Synchronization Facility (ASF) IBM BlueGene/Q 2013 – Intel Transactional Synchronization Extensions (TSX) Processador acaba de ser lançado (Haswell) Nosso foco A partir desse ponto não serão mais especificados dados técnicos de hardware (que esquema de cache ou protocolo foi usado)

134 IBM BlueGene/Q Usados no supercomputador Sequoia Each core
Revelado no Hot Chips 2011 18 16 cores para aplicações, 1 para SO, 1 aumentar yield Usados no supercomputador Sequoia Each core 1.47 Billion transistors 55 Watts Die yield The supercomputer may use as many as 100,000 of the chips. Running at 1.6 GHz, they deliver 204 Gflops at 55W, use 1.47 billion transistors and measure 19 x 19 mm. Each core is 4-way

135 Blue Gene/Q packaging hierarchy
4. Node Card 32 Compute Cards, Optical Modules, Link Chips, Torus 3. Compute Card One single chip module, 16 GB DDR3 Memory 2. Module Single Chip 1. Chip 16 cores 5b. I/O Drawer 8 I/O Cards 8 PCIe Gen2 slots 6. Rack 2 Midplanes 1, 2 or 4 I/O Drawers 7. System 20PF/s 5-D Topology: 16x16x16x12x2 A Q32 card is 2x2x2x2x2 and a midplane is 4x4x4x4x2. 5a. Midplane 16 Node Cards Slide produced by Martin Ohmacht for PACT 2011 Workshop Ref: SC2010

136 Suporte Para Operacões Atômicas na L2
IEEE Micro, March/April 2012

137 Example Thread 1 63

138 L2 structure 2MB / 16K lines ⇒1024 sets com 16 linhas/set 1024 sets
32 MB / 16 way set-associative / 128B por linha / 256K linhas na cache Ponto de coerência Cada fatia: 2MB / 16K lines ⇒1024 sets com 16 linhas/set 1024 sets Crossbar switch DEVBUS L2 L2 L2 L2 L2 L2 L2 L2 L2 L2 L2 L2 L2 L2 L2 L2 L2_counter L2_counter L2_counter L2_counter L2_central memory controller memory controller Slide adapted from Martin Ohmacht’s presentation at Workshop held at PACT 2011

139 Uma visão simplificada da organização lógica de um slice da L2 (32MB)
Speculative Bit Thread ID 128B Data 00…00 Index Tag 16K 11…11 16 ways 16K linhas x 16 ways x 128 Bytes = 32M Bytes

140 Diretório da L2 Para cada linha escrita especulativamente, o diretório
armazena um id da thread que é a dona. Quando ocorre um acesso a uma linha especulativa, o diretório compara a id da tread fazendo o acesso com a id da dona, e interrompe o thread mais jovem que está em conflicto. Se o conflito é entre uma thread especulativa e um thread não especulativa, a thread especulativa é abortada.

141 Speedup com relação à execução sequential

142

143 Conclusões Memória Transacional veio para ficar como um novo paradigma de programação paralela Duas grandes empresas na área lançaram processadores contendo extensões para TMs (IBM e Intel) Boas oportunidades para realização de pesquisa! Se estiver interessado em fazer pesquisa entre em contato!!

144 Obrigado!


Carregar ppt "IV Escola Regional de Alto Desempenho de São Paulo"

Apresentações semelhantes


Anúncios Google