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 las matrices grandes? ¿O incluso utilizando un simple openmp paralelo para bucles?En un código paralelo OpenMP, ¿habría algún beneficio para memset para ejecutarse en paralelo?
Respuesta
Las personas en HPC generalmente dicen 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. Here es un memsetter habilitado OpenMP rápido y sucio que escribí para usted que llena dos veces 2 GiB de memoria. Y aquí están los resultados usando GCC 4.7 con diferente número de roscas de diferentes arquitecturas (valores máximos de varias carreras reportado):
GCC 4.7, código compilado con -O3 -mtune=native -fopenmp
:
Quad-socket Intel Xeon X7350 - pre-Nehalem CPU de cuatro núcleos con controlador de memoria separada y bus frontal
solo zócalo
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
(primera tacto es lenta ya que el equipo de rosca está siendo creado a partir de cero y el sistema operativo es el mapeo de páginas físicas en el espacio virtual de direcciones reservadas por malloc(3)
)
Un hilo ya satura el ancho de banda de memoria de una sola CPU < -> NB enlace. (Puente NB = Norte)
1 hilo por socket
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
dos hilos son necesarios para saturar el ancho de banda de la memoria completa de la < NB -> enlace memoria.
socket Octo-Intel Xeon X7550 - sistema NUMA 8 vías con CPUs octo-núcleo (CMT discapacitados)
solo socket
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
Al menos 5 hilos son necesarios con el fin de saturar el ancho de banda de un enlace de memoria.
1 hilo por socket
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
ancho de banda escalas 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 proveedor neutral de lugares.
Editar: Para completar, aquí están los resultados de ejecutar el código con una matriz 1 GiB de aire de MacBook con Core i5-2557M Intel (de doble núcleo Sandy Bridge CPU con HT y QPI). El compilador es GCC 4.2.1 (versión Apple LLVM)
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)
se traduce por el compilador OS X a bzero(buf, len)
y que una versión vectorizada habilitada para SSE4.2 con el nombre bzero$VARIANT$sse42
es proporcionada por libc.dylib
y se usa en tiempo de ejecución. Utiliza la instrucción MOVDQA
para poner 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 AVX de un solo subproceso 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.
Gracias por estos resultados. ¿Cómo se controla "1 hilo/socket" o "todos los hilos en 1 socket"? –
Con 'taskset' y/o estableciendo la variable' GOMP_CPU_AFFINITY'. Si tiene 'hwloc' instalado, proporciona la ingeniosa herramienta' hwloc-ls'. Simplemente ejecútelo como 'hwloc-ls --taskset' y le mostrará la máscara de bits necesaria para' taskset', p. Ej. ejecutar en un solo socket. –
Esta es una gran respuesta. ¿Pero podría explicar más por qué hay tanta diferencia entre el primer toque y la reescritura? No entiendo completamente lo que quiere decir con "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 virtual reservado por malloc (3)" –
Bueno, siempre hay la memoria caché L3 ...
Sin embargo, es muy probable que este estará obligado por el ancho de banda de memoria principal ya; agregar más paralelismo es poco probable que mejore las cosas.
- 1. OpenMP - Ejecución de código paralelo dentro de código paralelo
- 2. OpenMP paralelo bucles for anidados vs paralelo interno para
- 3. paralelo código C para cálculo de distancia
- 4. OpenMP Codificación: aviso: ignorando #pragma omp paralelo
- 5. omp paralelo vs. omp paralelo para
- 6. openmp paralelo para bucle con dos o más reducciones
- 7. ¿Habría algún motivo para escribir código en binario puro?
- 8. Manejo de señales en el programa paralelo OpenMP
- 9. Código de JavaScript paralelo
- 10. ¿Cómo escribo tareas? (código paralelo)
- 11. el pragma "maestro" de OpenMP no debe estar encerrado en el pragma "paralelo para"
- 12. Xvfb pantallas múltiples para procesamiento en paralelo?
- 13. Algoritmo rápido para calcular Pi en paralelo
- 14. Cómo hacer un compilador paralelo para .NET
- 15. ¿Cómo se define la escalabilidad para el código paralelo?
- 16. Procesamiento paralelo en python
- 17. ¿Cuál es la mejor solución para un problema embarazosamente paralelo?
- 18. Procesamiento paralelo en Linux
- 19. Ejecutando funciones en paralelo
- 20. parallelsort paralelo en c
- 21. Crear un código existente en Java paralelo/multiproceso
- 22. ¿Hay un mapa paralelo basado en procesos simple para python?
- 23. procesamiento paralelo simple en perl
- 24. OpenMP: ¿Cuál es el beneficio de las paralelizaciones de anidamiento?
- 25. Mejor semilla para el proceso paralelo
- 26. Instalación en paralelo de Pip
- 27. Paralelo de un bucle for
- 28. ¿Cómo escribir código paralelo con vectores Haskell?
- 29. ScheduledExecutorService múltiples hilos en paralelo
- 30. Computación en paralelo en Haskell
Poco probable. 'memset' en datos fuera de caché probablemente será embotellado por ancho de banda de memoria. – Mysticial
Ejecutar 'memset' en paralelo en una máquina NUMA (y todos los sistemas MP post-Core2 Intel, así como todos los MP e incluso algunos sistemas UP AMD son NUMA) podría ser su asesino de rendimiento más difícil de entender por qué, a menos más adelante, los mismos hilos accederán solo a aquellas partes de la matriz que hayan puesto a cero personalmente. –
Sin embargo, existe el estándar de la industria [STREAM benchmark] (http://www.cs.virginia.edu/stream/). Coge la [versión de OpenMP] (http://www.cs.virginia.edu/stream/FTP/Code/Versions/stream_omp.c), compila y ejecuta con diferentes números de hilos para ver por ti mismo. Tenga en cuenta también que 'memset()' está habilitado para SIMD en la mayoría de las implementaciones 'libc' y ya está empujando el ancho de banda de la memoria a su punto máximo. –