Cuando los cálculos limitados de ancho de banda de memoria se llevan a cabo en entornos de memoria compartida (por ejemplo, roscado a través de OpenMP, Pthreads, o TBB), hay un dilema de cómo garantizar que la memoria se distribuye correctamente a través de física de la memoria, de tal manera que cada hilo en su mayoría los accesos a memoria en una bus de memoria "local". Aunque las interfaces no son portátiles, la mayoría de los sistemas operativos tienen formas de afinidad hilo de conjunto (por ejemplo, pthread_setaffinity_np()
en muchos sistemas POSIX, sched_setaffinity()
en Linux, SetThreadAffinityMask()
en Windows). Hay también librerías como hwloc para determinar la jerarquía de memoria, pero, por desgracia, la mayoría de los sistemas operativos todavía no proporcionan formas de políticas de la memoria NUMA conjunto. Linux es una notable excepción, con libnumapermitiendo que la aplicación manipule la política de memoria y la migración de página en granularidad de página (en línea principal desde 2004, por lo tanto ampliamente disponible). Otros sistemas operativos esperan que los usuarios observen una política implícita de "primer contacto".
Trabajar con una política de "primer contacto" significa que la persona que llama debe crear y distribuir hilos con cualquier afinidad que planeen usar más tarde cuando escriban por primera vez en la memoria recién asignada. (Muy pocos sistemas están configurados de modo que malloc()
realmente encuentren páginas, solo promete encontrarlas cuando realmente tienen fallas, tal vez por diferentes subprocesos). Esto implica que la asignación que usa calloc()
o inicializa inmediatamente la memoria después de la asignación memset()
es dañina ya que tenderá a fallar toda la memoria en el bus de memoria del núcleo ejecutando el hilo asignación, lo que lleva a un ancho de banda de memoria peor de los casos cuando se accede a la memoria desde varios subprocesos. Lo mismo se aplica al new
operador de C ++ que insiste en inicializar muchas asignaciones nuevas (p. Ej.std::complex
) Algunas observaciones acerca de este entorno:
- Asignación puede hacerse "colectivo hilo", pero ahora la asignación se convierte en mezcla en el modelo de hilos que es indeseable para las bibliotecas que pueden tener que interactuar con los clientes utilizando diferentes modelos de subprocesamiento (tal vez cada uno con sus propios conjuntos de subprocesos).
- RAII se considera una parte importante de C ++ idiomático, pero parece ser activamente perjudicial para el rendimiento de la memoria en un entorno NUMA. La ubicación
new
se puede utilizar con la memoria asignada a través demalloc()
o desde rutinaslibnuma
, pero esto cambia el proceso de asignación (que creo que es necesario). - EDITAR: Mi declaración anterior sobre el operador
new
era incorrecta, puede soportar múltiples argumentos, vea la respuesta de Chetan. Creo que todavía existe la preocupación de que las bibliotecas o los contenedores STL utilicen una afinidad específica. Campos múltiples pueden ser empacados y puede ser un inconveniente para que, por ejemplo, unstd::vector
reasigna con el gestor de contexto correcto activo. - Cada subproceso puede asignar y criticar su propia memoria privada, pero luego la indexación en regiones vecinas es más complicada. (Considere un producto de matriz-vector escaso con una partición de fila de la matriz y los vectores; indexar la parte no propietaria de requiere una estructura de datos más complicada cuando no es contigua en la memoria virtual).
Se ninguna solución a la asignación de la NUMA / inicialización considerados idiomática? ¿He dejado de lado otras trampas críticas?
(No me refiero a mi C ejemplos ++ dar a entender un énfasis en ese idioma, sin embargo, el C ++ lenguaje codifica algunas decisiones sobre la gestión de memoria que un lenguaje como C no, por lo tanto no tiende a ser más resistencia cuando lo que sugiere que los programadores de C ++ hacen los cosas diferentes)