Eu discordo de Matt sobre o uso total de memória para a malha e sobre indireção extra. Para métodos explícitos, é comum um número muito pequeno de vetores (por exemplo, 2) representar todo o estado da simulação. Para um problema escalar, apenas definir as coordenadas da malha pode ser mais do que isso e, se a conectividade for explícita, será substancialmente mais. Simulações de alta resolução com métodos explícitos frequentemente precisam de um grande número de etapas, portanto é comum usar subdomínios razoavelmente pequenos apenas para poder concluir uma execução do modelo em um período razoável de tempo. Portanto, não é tão comum ficar sem memória por causa do custo de armazenamento da malha.
Um problema muito mais fundamental é o requisito de largura de banda da memória para cada etapa de um algoritmo (por exemplo, avaliar um resíduo ou tomar um passo de tempo). Para métodos não estruturados, isso envolve algumas informações baseadas em malha, mas geralmente não requer acesso a todo o armazenamento em malha. Observe que, mesmo que a memória do solver domine a memória de malha (como é comum em muitos métodos implícitos), a simulação ainda depende frequentemente dos requisitos de largura de banda de uma passagem de malha. Existem dois fatores:
Quais dados são necessários durante a passagem de malha de rotina?
Nos métodos de elementos finitos, é necessário um mapa de elemento para global. Para valores superiores à primeira ordem, isso geralmente é implementado associando graus de liberdade a entidades intermediárias, como faces e arestas, mas depois que o mapa elemento para global é construído, as entidades intermediárias não são necessárias. Em particular, a conectividade das entidades intermediárias nunca é usada diretamente por uma simulação. Essas informações podem não ser tocadas para muitas iterações de um solucionador implícito ou integrador de tempo, mas ainda são necessárias durante a adaptabilidade ou para configurar um novo espaço de função. É comum configurar o mapa elemento para global em uma matriz simples e não tocar na estrutura de dados "malha" durante esse período; nesse caso, o formato de armazenamento e a localização dos dados da malha são menos importantes.
Os métodos de volume finito requerem volumes de célula, conectividade face a célula, área de face e normais de face. Observe que as coordenadas de vértices ou a conectividade não precisam estar disponíveis. Em exemplos extremos (por exemplo, FUN3D), todas as outras informações são descartadas durante o pré-processamento offline (geração de malha) e apenas essa representação reduzida está disponível para a simulação. Isso é muito eficiente, mas impede malhas em movimento e refinamento adaptativo.
Como esses dados são dispostos na memória?
Para muitas simulações, com ordens modestas de precisão e complexidade da física, o desempenho é limitado pela largura de banda da memória. As arquiteturas modernas de CPU da IBM, Intel, AMD e NVidia suportam uma intensidade aritmética entre 4 e 8 flops / byte. Com essas unidades de ponto flutuante eficientes, devemos tentar otimizar nossos algoritmos para a largura de banda da memória. Isso pode envolver mudanças algorítmicas um tanto profundas, como favorecer os métodos de alta ordem não montados (compare as linhas "montadas" e (não montadas) de "tensor" na segunda figura), mas podemos começar tentando utilizar totalmente a largura de banda fornecida pelo hardware. Isso geralmente envolve pedidos de computação (de vértices, faces, células etc.), para que o cache seja reutilizado da melhor maneira possível. Também envolve explorar o bloqueio e ordenar incógnitas para ter metadados mínimos e ativar o número certo de fluxos de dados. (Geralmente, a simulação não estruturada em 3D termina com "muitos" fluxos, mas alguns hardwares modernos, como o POWER7, precisam de muitos fluxos para saturar a largura de banda; portanto, ocasionalmente, faz sentido organizar intencionalmente dados para ativar mais fluxos.) O trabalho PETSc-FUN3D fornece um clássico , mas ainda é uma discussão altamente relevante sobre essa otimização de desempenho para CFD implícito não estruturado.
Sugestões para o seu problema
Não tenha medo malloc()
. Não há necessidade de compactar o refinamento atual da malha na mesma matriz que a parte inativa mais grossa da malha. Sempre que você não precisar mais de uma parte antiga da malha, apenas free()
ela.
Após o refinamento, calcule uma boa ordem para esse nível de refinamento ( Reverse Cuthill-McKee é popular, mas também podem ser usadas mais ordens específicas do cache). Se o seu desequilíbrio de carga for razoável (menos de 10%, por exemplo), você poderá calcular essa nova ordem localmente (sem particionamento e redistribuição paralelos). O custo provavelmente será semelhante a uma única passagem de malha "física", mas pode acelerar essas travessias por um fator de 2 ou mais. Essa etapa geralmente compensa sempre que a adaptabilidade da malha não ocorre em cada"física" malha transversal. Se essa adaptabilidade frequente for necessária para o seu problema, você provavelmente fará pequenas alterações e ainda deverá reordenar ocasionalmente. Eu ainda evitaria conjuntos de memórias refinadas porque dificulta a reordenação, mas você pode usar blocos razoavelmente grandes para equilibrar o uso máximo de memória, o custo de pedidos e o custo de atualizações incrementais.
Dependendo da discretização, considere extrair uma representação reduzida com poucos requisitos de largura de banda de memória para os percursos "físicos". Indireção desnecessária é ruim porque aumenta os requisitos de largura de banda e, se o destino da indireção for irregular, causa reutilização incorreta do cache e inibe a pré-busca.