En un código paralelo OpenMP, ¿habría algún beneficio para memset para ejecutarse en paralelo?

Tengo bloques de memoria que pueden ser bastante grandes (más grandes que la caché L2), y algunas veces debo configurarlos en cero. memset es bueno en un código de serie, pero ¿qué pasa con el código paralelo? ¿Alguien tiene experiencia si llamar a memset desde hilos concurrentes realmente acelera las cosas para arreglos grandes? ¿O incluso usando un simple openmp paralelo para bucles?

Las personas en HPC suelen decir que un hilo por lo general no es suficiente para saturar un único enlace de memoria, lo mismo suele ser cierto para los enlaces de red también. Aquí hay un memsetter habilitado para OpenMP rápido y sucio que escribí para ti que llena dos ceros con 2 GiB de memoria. Y aquí están los resultados usando GCC 4.7 con diferente número de subprocesos en diferentes architectures (valores máximos de varias ejecuciones informadas):

GCC 4.7, código comstackdo con -O3 -mtune=native -fopenmp :

Quad-socket Intel Xeon X7350 – CPU de cuatro núcleos pre-Nehalem con controlador de memoria separado y Front Side Bus

toma única

 threads 1st touch rewrite 1 1452.223 MB/s 3279.745 MB/s 2 1541.130 MB/s 3227.216 MB/s 3 1502.889 MB/s 3215.992 MB/s 4 1468.931 MB/s 3201.481 MB/s 

(El primer toque es lento ya que el equipo de subprocesos se está creando desde cero y el sistema operativo asigna páginas físicas al espacio de direcciones virtuales reservado por malloc(3) )

Un hilo ya satura el ancho de banda de la memoria de un único enlace CPU < -> NB. (NB = North Bridge)

1 hilo por zócalo

 threads 1st touch rewrite 1 1455.603 MB/s 3273.959 MB/s 2 2824.883 MB/s 5346.416 MB/s 3 3979.515 MB/s 5301.140 MB/s 4 4128.784 MB/s 5296.082 MB/s 

Se necesitan dos hilos para saturar el ancho de banda de la memoria completa del enlace de memoria NB < ->.

Octo-socket Intel Xeon X7550 – Sistema NUMA de 8 vías con CPU octo-core (CMT desactivado)

toma única

 threads 1st touch rewrite 1 1469.897 MB/s 3435.087 MB/s 2 2801.953 MB/s 6527.076 MB/s 3 3805.691 MB/s 9297.412 MB/s 4 4647.067 MB/s 10816.266 MB/s 5 5159.968 MB/s 11220.991 MB/s 6 5330.690 MB/s 11227.760 MB/s 

Se necesitan al menos 5 subprocesos para saturar el ancho de banda de un enlace de memoria.

1 hilo por zócalo

 threads 1st touch rewrite 1 1460.012 MB/s 3436.950 MB/s 2 2928.678 MB/s 6866.857 MB/s 3 4408.359 MB/s 10301.129 MB/s 4 5859.548 MB/s 13712.755 MB/s 5 7276.209 MB/s 16940.793 MB/s 6 8760.900 MB/s 20252.937 MB/s 

El ancho de banda escala casi linealmente con el número de hilos. Con base en las observaciones de un solo socket, se podría decir que al menos 40 hilos distribuidos como 5 hilos por zócalo serían necesarios para saturar los ocho enlaces de memoria.

El problema básico en los sistemas NUMA es la política de memoria de primer toque: la memoria se asigna en el nodo NUMA donde se ejecuta el subproceso primero para tocar una dirección virtual dentro de una página específica. La fijación de subprocesos (vinculación a núcleos de CPU específicos) es esencial en dichos sistemas, ya que la migración de subprocesos conduce al acceso remoto, que es más lento. Compatible con pinnig está disponible en la mayoría de los tiempos de ejecución de OpenMP. GCC con su libgomp tiene la variable de entorno GOMP_CPU_AFFINITY , Intel tiene la variable de entorno KMP_AFFINITY , etc. Además, OpenMP 4.0 introdujo el concepto de lugares neutral para el proveedor.

Editar: para completar, aquí están los resultados de ejecutar el código con una matriz de 1 GiB en MacBook Air con Intel Core i5-2557M (CPU Sandy Bridge de doble núcleo con HT y QPI). El comstackdor es GCC 4.2.1 (Apple LLVM build)

 threads 1st touch rewrite 1 2257.699 MB/s 7659.678 MB/s 2 3282.500 MB/s 8157.528 MB/s 3 4109.371 MB/s 8157.335 MB/s 4 4591.780 MB/s 8141.439 MB/s 

¿Por qué esta alta velocidad con un solo hilo? Una pequeña exploración con gdb muestra que memset(buf, 0, len) es traducido por el comstackdor OS X a bzero(buf, len) y que una versión vectorizada habilitada para SSE4.2 con el nombre de bzero$VARIANT$sse42 es provista por libc.dylib y utilizado en tiempo de ejecución. Utiliza la instrucción MOVDQA para MOVDQA a cero 16 bytes de memoria a la vez. Es por eso que incluso con un hilo, el ancho de banda de la memoria está casi saturado. Una versión habilitada AVX de subproceso único que utiliza VMOVDQA puede cero 32 bytes a la vez y probablemente saturar el enlace de memoria.

El mensaje importante aquí es que a veces la vectorización y el multihilo no son ortogonales para acelerar la operación.

Bueno, siempre está el caché L3 …

Sin embargo, es muy probable que esto ya esté vinculado al ancho de banda de la memoria principal; agregar más paralelismo es poco probable que mejore las cosas.