Quando cálculos limitados de largura de banda de memória são executados em ambientes de memória compartilhada (por exemplo, encadeados via OpenMP, Pthreads ou TBB), há um dilema de como garantir que a memória seja distribuída corretamente na memória física , de modo que cada encadeamento acesse a memória em um barramento de memória "local". Embora as interfaces não sejam portáveis, a maioria dos sistemas operacionais possui maneiras de definir a afinidade do encadeamento (por exemplo, pthread_setaffinity_np()
em muitos sistemas POSIX, sched_setaffinity()
no Linux, SetThreadAffinityMask()
no Windows). Também existem bibliotecas, como hwloc, para determinar a hierarquia de memória, mas, infelizmente, a maioria dos sistemas operacionais ainda não fornece maneiras de definir políticas de memória NUMA. Linux é uma exceção notável, com libnumapermitindo que o aplicativo manipule a diretiva de memória e a migração de páginas com granularidade de páginas (na linha principal desde 2004, portanto amplamente disponível). Outros sistemas operacionais esperam que os usuários observem uma política implícita de "primeiro toque".
Trabalhar com uma política de "primeiro toque" significa que o chamador deve criar e distribuir threads com qualquer afinidade que planeja usar posteriormente ao gravar na memória recém-alocada. (Pouquíssimos sistemas são configurados de modo que malloc()
encontrem páginas, eles apenas prometem encontrá-las quando estão com falhas, talvez por threads diferentes.) Isso implica que a alocação usando calloc()
ou inicializando imediatamente a memória após a alocação memset()
é prejudicial, pois tenderá a falhar toda a memória no barramento de memória do núcleo executando o encadeamento de alocação, levando à pior largura de banda da memória quando a memória é acessada de vários encadeamentos. O mesmo se aplica ao new
operador C ++ , que insiste em inicializar muitas novas alocações (por exemplo,std::complex
) Algumas observações sobre esse ambiente:
- A alocação pode ser "coletiva de encadeamentos", mas agora a alocação se torna misturada no modelo de encadeamento, o que é indesejável para bibliotecas que talvez precisem interagir com clientes usando diferentes modelos de encadeamento (talvez cada um com seus próprios conjuntos de encadeamentos).
- O RAII é considerado uma parte importante do C ++ idiomático, mas parece ser prejudicial ao desempenho da memória em um ambiente NUMA. O posicionamento
new
pode ser usado com memória alocada viamalloc()
ou rotinas delibnuma
, mas isso altera o processo de alocação (que eu acredito que é necessário). - Edição: minha declaração anterior sobre o operador
new
estava incorreta, ele pode suportar vários argumentos, consulte a resposta de Chetan. Acredito que ainda exista uma preocupação em obter bibliotecas ou contêineres STL para usar afinidade especificada. Vários campos podem ser compactados e pode ser inconveniente garantir que, por exemplo, sejastd::vector
realocado com o gerenciador de contexto correto ativo. - Cada encadeamento pode alocar e danificar sua própria memória privada, mas a indexação nas regiões vizinhas é mais complicada. (Considere um produto de vetor de matriz esparsa com uma partição de linha da matriz e vetores; a indexação da parte não proprietária de x requer uma estrutura de dados mais complicada quando x não é contíguo na memória virtual.)
Alguma solução para alocação / inicialização do NUMA é considerada idiomática? Eu deixei de fora outras dicas críticas?
(Não quero que meus exemplos de C ++ impliquem ênfase nessa linguagem; no entanto, a linguagem C ++ codifica algumas decisões sobre gerenciamento de memória que uma linguagem como C não faz, portanto, tende a haver mais resistência ao sugerir que os programadores de C ++ façam essas as coisas de maneira diferente.)