Paradigmas de Computação Paralela (UCE Computação Paralela Distribuída) Modelos de consistência de memória João Luís Ferreira Sobral jls@... 29 Março 2011
Resumo Revisão: modelos de threads Qual a necessidade do modelos de consistência? Modelos de consistência sequencial Modelos de consistência relaxada Consistência fraca Consistência na libertação (release) Estudo de casos OpenMP Java Threads
Execução de programas multi-threaded em uniprocessadores (unicore) O processador executa todos os fios de execução do programa A política de escalonamento não é especificada As operações efectuadas por cada fio de execução são executadas por ordem Sincronização entre fios de execução através de operações atómicas (c/ locks) As instruções executadas por cada fio são intercaladas Não determinismo: o resultado do programa depende da forma como são escalonados os fios de execução; ex: Thread 1 Thread 2.. x := 1; print(x); x := 2;
Execução de programas multi-threaded em muli-processadores Cada processador executa um fio de execução do programa (consideramos o número de fios igual ao número de processadores) As operações efectuadas por cada fio de execução são executadas por ordem Um processador pode aceder à memória global para fazer load/store/operações atómicas Não há caching da dados globais Os resultados possíveis de um programa são iguais aos de um uniprocessador. Considerações mais realistas: Caching de dados globais para melhorar a eficiência Requer um protocolo de coesão de caches Execução fora de ordem de instruções Pode alterar a semântica do programa => obriga a um modelo de consistência de memória
Execução fora de ordem de instruções (execution out-of-order ) Os processadores reordenam as instruções para melhorar o desempenho As re-ordenações devem respeitar as dependências Dados e.g., loads/stores devem ser executados pela ordem especificada Controlo As re-ordenações podem ser efectuadas pelo processador ou pelo compilador Re-odenações permitidas Stores em diferentes localizações store v1, data store b1, flag store b1, flag ß à store v1, data Loads de diferentes localizações load flag, r1 load data,r2 load data, r2 ß à load flag, r1 Loads e stores em diferentes localizações
Execução fora de ordem de instruções Exemplo de reordenação efectuada pelo hardware Load bypassing Store buffer Memory system O store buffer armazena operações de store para a enviar para a memória Os loads têm prioridade sobre os stores para manter o processador ocupado Ultrapassam os stores no buffer Os loads verificam se existe um store nesse mesmo endereço Resultado: os load e stores não são efectuados pela ordem do programa
Execução fora de ordem de instruções em multiprocessadores Modelo canónico Operações realizadas por um dado processador são efectuadas por ordem As operações realizadas em memória por vários processadores são intercaladas Se um processador reordenar o seu fluxo de instruções, será que a execução produz o mesmo resultado que o modelo canónico? Inicialmente A = Flag = 0 P1 P2 A = 23; while (Flag!= 1) {;} Flag = 1;... = A; P1 escreve dados em A e assinala isso a P2 através da Flag P2 espera até a Flag estar activa, então lê o A O que acontece se P1 trocar a ordem dos stores?
Execução fora de ordem de instruções em multiprocessadores Exemplo II Flag1 = Flag2 = 0 P1 P2 Flag1 = 1; Flag2 = 1; If (Flag2 == 0) If (Flag1 == 0) critical section critical section Uma possível ordem de execução P1 P2 Write Flag1, 1 Write Flag2, 1 Read Flag2 //get 0 Read Flag1 // get 1? => só se os load e stores forem efectuados por ordem
Conclusões Modelos de consistência de memória Os sistemas uni-processador podem reordenar as instruções para melhorar o desempenho, respeitando as dependências de dados e de controlo (locais!) Este pressuposto não é válido para ambientes multi-processador de memória partilhada Um programa paralelo pode produzir resultados contra-intuitivos Quais as limitações que se devem impor à reordenação de instruções para que: A programação seja intuitiva Não se perca o desempenho uni-processador Solução: modelo de consistência de memória suportado pelo processador Modelo de consistência sequencial (o mais simples): O processador não pode reordenar as leituras e escritas na memória global Reduz o desempenho uni-processador
Modelos de consistência relaxada (relaxed consistency) Consistência fraca (weak consistency) O programador especifica regiões onde as operações devem ser ordenadas Instruções de fence As operações antes de fence devem completar antes do fence ser executado As operações depois de fence têm que esperar que o fence termine. Os fences são executados por ordem Implementação de fence Contador incrementado a cada iniciação de uma operação de fence Exemplo: Instrução SYNC no PowerPC flush em OpenMP Operações de sincronização do tipo lock/unlock fence fence As operações nestas regioes podem ser reordenadas Ordem de execução fence
Modelos de consistência relaxada Consistência fraca (weak consistency) Exemplo I - Revisto Inicialmente A = Flag = 0 P1 P2 A = 23; flush while (Flag!= 1) {;} Flag = 1;... = A; P1 escreve dados em A O flush espera que a escrita de A seja completada P1 escreve dados na Flag P2 vai ler Flag==1 quando a escrita em A completou, mesmo que as operações antes do flush sejam executadas fora de ordem Será necessário um flush entre as duas operações em P2?
Modelos de consistência relaxada Consistência de release Mais relaxada que a consistência fraca O modelo de consistência fraca não diferencia entre leituras e escritas (a instrução de fence implica a escrita/leitura de todos os valores alterados) O modelo de consistência de libertação atualiza os valores locais (lidos da memória) à entrada da região crítica e escreve os valores na memória à saída da região. Acessos de sincronização divididos em: Aquisição: operações do tipo lock deve ser efectuada antes realizar acessos à memória não espera pelos acessos que a precedem (1) (1) L/S ACQ Libertação: operações do tipo unlock Deve ser efetuada após todas as escritas Acessos após o release não têm que esperar pelo release (2) L/S REL L/S (2)
Implementação em processadores actuais
Modelos de consistência Impõe restrições à reordenação de instruções de um processador Não estão relacionadas com as operações na memória de diferentes processadores Existem muitos modelos de consistência de memória Consenso emergente em torno de fraco/ release É fácil escrever um programa cujo comportamento seja dependente do modelo de consistência de memória utilizado Coesão da memória (visto anteriormente) Cria a ilusão de uma só localização lógica correspondente a cada variável do programa mesmo que existam múltiplas localizações para essa variável
Modelo do OpenMP Baseia-se num modelo de consistência fraca Os fios de execução podem ter uma vista temporária da memória (registos, cache, etc..) As escritas e leituras podem ser efectuadas na memória local Instrução fence #pragma omp flush Força a consistência entre a vista temporária e a memória (para a lista de variáveis fornecida no flush) As variáveis alteradas localmente são escritas na memória As cópias locais de variáveis são descartadas para que a próxima leitura seja efectuada da memória A operação termina quando todas as variáveis foram actualizadas na memória Não é garantida a ordenação entre flush se a lista de variáveis for disjunta A memória/variáveis privadas (threadprivate, private) não são afectadas pelo flush O flush é implícito em: Barrier, set_lock, unset_lock, atomic Entrada e saída de parallel, critical e ordered A sincronização através de variáveis em memória é desaconselhada
Modelo do Java Modelos de consistência de memória O modelo original foi revisto em 2004 (Java 5.0) por limitar demasiado as optimizações do compilador e não fornecer garantias suficientes ao programador Baseia-se num modelo de consistência relaxada (ordenação parcial de eventos) Os fios de execução podem manter cópias locais dos dados que não estão sincronizadas com os valores na memória. As variáveis declaradas como voláteis não são armazenadas localmente Memória partilhada entre fios de execução Variáveis de instância, variáveis estáticas, elementos de arrays Variáveis locais, parâmetros e handler de excepções não são partilhados. Regiões synchronized: Na entrada invalida cópias locais dos dados Na saída faz flush das variáveis Implica ordenação entre regiões consecutivas (protegidas pelo mesmo lock!) Problema do modelo antigo: não força a semântica sequencial de outras variáveis relativamente às voláteis Inicialmente A = Flag = 0 Flag declarada como volátil P1 P2 A = 23; while (Flag!= 1) {;} Flag = 1;... = A; P1 escreve dados em A e assinala isso a P2 através da Flag P2 espera até a Flag estar activa, então lê o A